Are there any issues with using `async`/`await` with `forEach()` loops in JavaScript?
David Y.
—Consider the following JavaScript code:
const requests = await getRequests(); await requests.forEach(async (request) => { await process(request); }); await notifyCompletion("All requests processed.");
At first glance, this code appears straightforward. We retrieve a set of request
objects, use a forEach()
loop to process them, and call notifyCompletion()
once our processing is done. All of our custom functions are async
, so we correctly await
their results.
But if we run this code, we may start to encounter unexpected behavior. What’s wrong with it?
The main problem is that Array.prototype.forEach()
predates the implementation of the async
/await
pattern, which relies on Promise
s. forEach()
does not return a Promise
, so the await
in front of requests.forEach()
will do nothing.
A second problem is that await
in await process(request)
is inside a callback function, and thus will not halt execution of the outer function where forEach()
resides. forEach()
will invoke its callback function for each request
in requests
as quickly as possible, without waiting for any of the previous process()
calls to return. This means our requests will be processed in parallel rather than in sequence, which may or may not be what we want.
An additional, more serious consequence of the forEach()
loop not pausing execution to wait for the results of process()
, is that our code may execute notifyCompletion()
before all requests have been fully processed. In the best case scenario, our code will send a completion notification slightly before all requests have been handled; in the worst, our code will send a completion notification slightly before crashing from an uncaught error in one of the process()
calls.
How can we rewrite this code to do what it appears to be doing? There are a couple of different approaches we can take, depending on whether we would like requests to be processed in sequence or in parallel.
To process requests in sequence, we can replace forEach()
with JavaScript’s for...of
statement (introduced in 2015):
const requests = await getRequests(); for (const requests of requests) { await process(request); } await notifyCompletion("All requests processed.");
Individual iterations of this loop are part of the same function as the surrounding code, and so the await
inside the loop will cause execution to pause on every iteration of the loop. notifyCompletion()
will only execute once all iterations of the loop have completed.
To process requests in parallel, we can use Promise.all()
and Array.prototype.map()
:
const requests = await getRequests(); await Promise.all(requests.map(async (request) => { await process(request); })); await notifyCompletion("All requests processed.");
Promise.all()
, unlike forEach()
, can be await
ed, and so we can be assured that all of our requests will be processed before notifyCompletion()
is called.
Tasty treats for web developers brought to you by Sentry. Get tips and tricks from Wes Bos and Scott Tolinski.
SEE EPISODESConsidered “not bad” by 4 million developers and more than 100,000 organizations worldwide, Sentry provides code-level observability to many of the world’s best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.
Here’s a quick look at how Sentry handles your personal information (PII).
×We collect PII about people browsing our website, users of the Sentry service, prospective customers, and people who otherwise interact with us.
What if my PII is included in data sent to Sentry by a Sentry customer (e.g., someone using Sentry to monitor their app)? In this case you have to contact the Sentry customer (e.g., the maker of the app). We do not control the data that is sent to us through the Sentry service for the purposes of application monitoring.
Am I included?We may disclose your PII to the following type of recipients:
You may have the following rights related to your PII:
If you have any questions or concerns about your privacy at Sentry, please email us at compliance@sentry.io.
If you are a California resident, see our Supplemental notice.