Using reduce with nested async/await

Khyati Thakur
Mar 2, 2018 · 3 min read

I came across an issue at work where I had to use reduce inside nested async/await calls. Although, I have to admit, I did not solve it entirely on my own, this is the solution that I found for the issue at hand.

What the problem was

Suppose you have an original Array , say, something like this

const originalArray = [
{id: 560, name: 'abc', year: '1990'},
{id: 600, name: 'def', year: '2006'},
{id: 601, name: 'ghi', year: '2009'}
]

You have to clone this array and create new records in the database, however, you have to use this mapping to update the records from the old year to the new year and return a flat array. The mapping is :

const mapping = [
{old: '1990', new: '1998'},
{old: '2006', new: '2016'},
{old: '2009', new: '2019'}
];

Initial Solution

My initial solution was to run reduce over the mapping and then run reduce over the originalArray and return an array of promises, which gets concatenated in my accumulator and resolve it using Promise.all.

So that’s what I did. It looked something like this :

// Run reduce over the mapping and put the result of the database creating call in an empty array and return the arrayconst newPromises = () => {
const newDataArray = mapping.reduce((acc, data) => {
const newData = await createNewRecords(data); return acc.concat(newData); }, []);
return newDataArray;
}

Creating the records in the database for every mapping looked like :

// Run reduce over the originalArray for every records in the mapping, create a database record , put the result of the database creation in an empty array and return the resolved Promise.const createNewRecords = async (data) => {const createdData = originalArray.reduce((acc, data) => {const newData = await db.create({
...data,
year: data.year
});
return acc.concat(newData);
}, [])return Promise.all(createData);}

You would think that after creating the record, promise.all will return the resolved arrays and acc.concat , will concat the result into 1 single array.

Why the solution did not work

The issue here is that db.create is an async function. It always returns a promise. Since it returns a promise, the value of our accumulator has to be a promise, otherwise it throws an error on concatenating.

The Ultimate Fix

This is how I fixed the issue.

Since the result of ‘createNewData’ function is a Promise, our accumulator should be a Promise, that we can concatenate and return the resolved result.

const newPromises = () => {
const newDataArray = mapping.reduce(async(acc, data) => {
const accumulator = await acc.resolve();
const newThings = await createNewData(data); return Promise.resolve([...accumulator, ...newThings])}, Promise.resolve([])); return newDataArray;
}

Creating records in the database with an async function looked like:

const createNewData = (data) => {    originalArray.reduce(async(acc, data) => {
const accumulator = await acc.resolve();

const newData= await db.create({...data});
accumulator.push(newData); return Promise.resolve(accumulator);}, Promise.resolve([])) return createNewData;}

Just to keep in mind, when using reduce with async calls, since the result of the async call will be a Promise, always remember to make your accumulator a Promise as well, so you can create a collection of promises in your accumulator and return the resolved Promise array.

Last Thoughts

It was a fairly complex issue that took a while for me to figure out. I have posted this on the blog so that I can remember it later, if I stumble upon it again. Also, I wanted to help people who will or have run into this issue before.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade