JavaScript Array .Reduce with Async/Await

JavaScript array .reduce with async/await

The problem is that your accumulator values are promises - they're return values of async functions. To get sequential evaluation (and all but the last iteration to be awaited at all), you need to use

const data = await array.reduce(async (accumP, current, index) => {
const accum = await accumP;

}, Promise.resolve(…));

That said, for async/await I would in general recommend to use plain loops instead of array iteration methods, they're more performant and often simpler.

Array reduce function with async await

In your first try, result is a promise as all async functions evaluate to a promise when called, so you have to await result before you can push to the array, and then you don't need the Promise.all:

 newObjectArray = await objectAray.reduce(async (result, el) => {
const asyncResult = await someAsyncTask(el);
if (asyncResult) {
(await result).push(newSavedFile);
}
return result;
}, []);

But I'd guess that it is way faster to just filter afterwards:

 newObjectArray = (await Promise.all(objArray.map(someAsyncTask))).filter(el => el);

Typescript array reduce with async/await showing type mismatch error

Since the callback is async, it'll always return a Promise; the type parameter should not be Person[], but Promise<Person[]>. You also need to pass such a value as the initial value, and wait for the prior accumulator to resolve before concating.

const ageByPersonPromise = persons.reduce<Promise<Person[]>>(
async (resultPromise, person) => {
const result = await resultPromise;
await sleep(4000);
return result.concat({
age: person.age,
name: person.name,
});
},
Promise.resolve([])
);
ageByPersonPromise
.then((ageByPerson) => {
console.log('age ', ageByPerson);
});

Reduce with async logic

Given that your reduce callback is async, the returned result is a promise. Notice how your log of result is a Promise. To successfully access the value from the previous iteration in the current, you must await that value first.

So instead of

result.readyScoreRequests.push(scoreInfo)

You should await the result to get the inner value.

const resultRes = await result
resultRes.readyScoreRequests.push(scoreInfo)

Here is a simplified example of this that should help to clarify:

function multiply(value){
return new Promise(res => {
setTimeout(() => {
res(value*2)
}, 100)
})
}

async function runSum(values){
const sum = await values.reduce(async (acc, cur) => {
const value = await multiply(cur)
const accRes = await acc
return accRes + value
}, 0)
console.log(sum)
}

runSum([1,2,3,4])

initialization in async reduce

An async function returns Promise, which makes it difficult to work with when using .reduce() as you would need to await your accumulator each iteration to get your data. As an alternative, you can create an array of Promises using the mapper function of Array.from() (which you can think of as using .map() directly after Array.from()). The idea here is that the map will trigger multiple asynchronous calls for each file by using sendImage/sendFile. These calls will run in parallel in the background. The value that we return from the mapping function will be a Promise that notifies us when the asynchronous call has successfully completed (once it resolves). Moreover, the mapping function defines what the promise resolves with, in our case that is the new object with the src property:

const isImage = file => file.type.startsWith('image/');
const filePromises = Array.from(target, async file => {
const response = await (isImage(file) ? channel.sendImage(file) : channel. sendFile(file));
return {...file, type: file.type, src: response.file};
});

Above filePromises is an array of Promises (as the async mapper function returns a Promise implicitly). We can use Promise.all() to wait for all of our Promises to resolve. This is faster than performing each asynchronous call one by one and only moving to the next once we've waited for the previous to complete:

setIsLoading(true); // set loading to `true` before we start waiting for our asynchronous work to complete
const fileObjects = await Promise.all(filePromises);
setIsLoading(false); // complete asynchronous loading/waiting

Lastly, fileObjects is an array that contains all objects, both files and images. We can do one iteration to partition this array into seperate arrays, one for images, and one for files:

const attachments = {files: [], images: []};
for(const fileObj of fileObjects) {
if(isImage(fileObj))
attachments.images.push(fileObj);
else
attachments.files.push(fileObj);
}

using async await with reduce

The problem is that acc on the second iteration is a promise object - that's why you await it. However, you still assign the [cur] property on that promise object, not on the promise result, and the implicit promise chaining of the async function as well as the explicit awaits will just ignore properties on the promise object. You could fix this by doing acc = await acc;, but really I recommend not to use reduce with async/await at all. A normal loop is much simpler and has no pitfalls.

const imageField = ["logoUrl", "fullLogoUrl"]
const onCreate = async (submitData: any) => {
const uploadImageField = {};
for (const cur of imageField) {
const url = await uploadImage(submitData.general[cur][0]);
acc[cur] = url;
}
console.log(uploadImageField);
}

Why reduce with async function returns only the last object

You're very close. The main issue is that you wrote the array as [(...)] instead of [...]. In your code, the array only contains one value, if you remove the () you will have three values. See comma operator for details.

The other thing you have to consider is that an async function always returns a promise, so you must await o as well -

(async () =>
console.log(
await ["apple", "banana", "orange"].reduce(async (o, fruit) => {
const rand = new Promise((resolve) => setTimeout(() => resolve(Math.random()), 200))
return { ...await o, [fruit]: await rand }
}, {})
))();


Related Topics



Leave a reply



Submit