How to Use Async with Foreach

Using async/await with a forEach loop

Sure the code does work, but I'm pretty sure it doesn't do what you expect it to do. It just fires off multiple asynchronous calls, but the printFiles function does immediately return after that.

Reading in sequence

If you want to read the files in sequence, you cannot use forEach indeed. Just use a modern for … of loop instead, in which await will work as expected:

async function printFiles () {
const files = await getFilePaths();

for (const file of files) {
const contents = await fs.readFile(file, 'utf8');
console.log(contents);
}
}

Reading in parallel

If you want to read the files in parallel, you cannot use forEach indeed. Each of the async callback function calls does return a promise, but you're throwing them away instead of awaiting them. Just use map instead, and you can await the array of promises that you'll get with Promise.all:

async function printFiles () {
const files = await getFilePaths();

await Promise.all(files.map(async (file) => {
const contents = await fs.readFile(file, 'utf8')
console.log(contents)
}));
}

await with array foreach containing async await

Use Array.prototype.map and Promise.all:

async function procesMultipleCandidates (data) {
let generatedResponse = []
await Promise.all(data.map(async (elem) => {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
// and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
}))
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}

Or use a for/of loop if you don't want the loop run concurrently:

async function procesMultipleCandidates (data) {
let generatedResponse = []
for(let elem of data) {
try {
// here candidate data is inserted into
let insertResponse = await insertionInCandidate(elem)
// and response need to be added into final response array
generatedResponse.push(insertResponse)
} catch (error) {
console.log('error'+ error);
}
}
console.log('complete all') // gets loged first
return generatedResponse // return without waiting for process of
}

What is the difference between async/await forEach and Promise.all + map

The O.P.'s question was asking for clarification on another StackOverflow question found here. For further reading, and many other great answers on this general topic, please take a look at the link.

For Googlers who only saw the question title

Don't use async/await with forEach. Either use a for-of loop, or use Promise.all() with array.map().

If you have a general understanding on promises and async/await, the TL;DR on the differences between promise.all() + array.map() and .forEach(), is that it's impossible to await a forEach() itself. Yes, you can run tasks in parallel in .forEach() just like you can with .map(), but you can't wait for all of those parallel tasks to finish, then do something once they've all finished. The whole point of using .map() instead of .forEach() is so you can get a list of promises, collect them with Promise.all(), then await the whole thing. To see what I mean, just put a console.log('Finished') after a forEach(async () => ...), and you'll see the "finished" get logged out before everything has finished running in the .forEach() loop. My advice would be to just not use .forEach() with async logic (and really there's not a reason use ever use .forEach() anymore these days, as I explain further down).

For those who need something a bit more in depth, the rest of this answer will dive into more detail, first giving a small review on promises, then showing an in-depth explanation on how these approaches behave differently and why .forEach() is always the inferior solution when it comes to async/await.

A primer on promises and async/await

For the purposes of this discussion, you just have to remember that a promise is a special object that's promising that some task is going to be completed at some point in the future. You can attach listeners to a promise via .then(), to get notified when the task is completed, and to receive the resolved value.

An async function is simply a function that will always return a promise, no matter what. Even if you do async function doThing() { return 2 }, it's not going to return 2, it's going to return a promise that immediately resolves to the value 2. Note that an asynchronous function will always return a promise immediately, even if it takes a long time for the function to run. This is why it's called a "promise", it's promising that the function will eventually finish running, and if you want to be notified for when the function has finished, you can add an event listener to it, via .then() or await.

await is special syntax that lets you pause execution of an async function until a promise resolves. await will only effect the function it's directly inside. Behind the scenes, await is simply adding a special event listener to the promise's .then(), so it can know when the promise resolves and what value it resolves with.

async fn1() {
async fn2() {
await myPromise // This pauses execution of fn2(), not fn1()!
}
...
}

async function fn1() {
function fn2() {
await myPromise // An error, because fn2() is not async.
}
...
}

If you can get a good grasp of these principles, then you should be able to understand the next sections.

for-of

A for-of loop lets you execute your asynchronous tasks in serial, one after another. For example:

const delays = [1000, 1400, 1200];

// A function that will return a
// promise that resolves after the specified
// amount of time.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))

async function main() {
console.log('start')
for (const delay of delays) {
await wait(delay)
console.log('Finished waiting for the delay ' + delay)
}
console.log('finish')
}

main()

How to properly use async in forEach in nodeJS

Instead of .forEach, use .map to map each item in someArray to a Promise, so that you can create an array of Promises. If you call Promise.all on that array, you'll get a Promise that resolves when all of the promises in the array have resolved.

Because findById is callback-based, not Promise-based, you need to transform each callback into a Promise first:

const promises = someArray.map((item) => new Promise((resolve, reject) => {
Collection.findById(item.id, (err,data) => {
if (err) return reject(err);
resolve(data.record);
});
}));
Promise.all(promises)
.then(arrayToSend => {
res.render("index", { arrayToSend });
})
.catch((err) => {
// handle errors
});

If you want to tolerate errors from findById and still build an arrayToSend without the items that had errors, you can push to an external array (similar to what you were doing originally) and resolve unconditionally, whether findById results in an error or not:

const arrayToSend = [];
const promises = someArray.map((item) => new Promise((resolve, reject) => {
Collection.findById(item.id, (err,data) => {
if (err) console.log(err);
else arrayToSend.push(data.record);
resolve();
});
}));
Promise.all(promises)
.then(() => {
res.render("index", { arrayToSend });
})

Use async / await with fetch and foreach

Function fetchErrors is not async here, because it does not return Promise. And since you try to call the function in the constructor async/await syntax won't really work in this context.

What you need to do instead is to use Promise callback here. You can apply the Promise.allSettled method. It will help you to wait until your requests will get resolved and then you can handle the responses one by one.

constructor() {
// ...

const errors = [];

this.fetchErrors().then(results => {
results.forEach(result => {
if (result.status === "rejected") {
errors.push(result.value)
}
})

console.log(errors); // will print you list of errors
});

}

protected fetchErrors() {
const requests = this.linksObjects
.filter(linkObject => linkObject.url.protocol !== 'javascript:')
.map((linkObject) => fetch(linkObject.url.href))

return Promise.allSettled(requests);
}

showing empty array in after pushing data from foreach into it in asynchronous function

The forEach function is not async-aware. You will need to use a simple for loop:

for( let i = 0; i < commandbody.length; i++ ) {
let arrayC = await commandsModel.getbyId(command);
cmdArray.push(arrayC);
}
console.log(cmdArray);

This should work if your outer function is marked as async too.

Use async await in forEach loop, node js

.forEach() is not async-aware. Use a plain for/of loop instead. In fact, pretty much stop using .forEach() entirely these days as its basically obsolete. With block scope available now with let and const, there is no reason to use .forEach() and it's extra function scope any more. Use a regular for loop instead.

You can fix this particular code by changing to this:

exports.filterAverageRatingsEachSkillForStudent = async (req, res) => {
let receiver = req.query.receiver; //array of id ['ABBd23', 'SDSd242', 'dfNJ47']
try {
console.log('receiver id', receiver);

for (let id of reciever) {
console.log('Query String ', id);
const findRating = await FeedbackSchema.find({
receiver: id,
}).select('ratingL ratingS ratingR ratingW');
}
} catch (error) {
console.log(error.message);
}
}

It also appears that you must not have disclosed all the code for this function because it looks like you're trying to get findRating, but then you never do anything with it.

The error you report about cannot set headers after they are sent does not occur in this code since that error is about sending multiple responses to the same incoming request and you don't send any responses in this code. So, help with that particular error would require disclosing more code or perhaps fixing this function will also fix that (we can't tell since we can't see the code that actually causes that error).



Related Topics



Leave a reply



Submit