18
5 Async/Await Design Patterns for Cleaner Async Logic
At Mastering JS, we love async/await. You might even say we wrote the book on async/await. Here's 5 design patterns we use regularly.
Async forEach()
Do not use an async callback with forEach()
. In general, the way to simulate forEach()
with async functions is to use await Promise.all([arr.map(callback)])
const values = [10, 50, 100];
// Do this:
await Promise.all(values.map(async v => {
await new Promise(resolve => setTimeout(resolve, v));
console.log('Slept for', v, 'ms');
}));
// Not this:
values.forEach(async v => {
await new Promise(resolve => setTimeout(resolve, v));
console.log('Slept for', v, 'ms');
});
Async/await works with try/catch
... almost. There's a gotcha. If you await
on a promise that rejects, JavaScript throws an error that you can catch
. But if you return
a promise that rejects, that ends up as an unhandled promise rejection.
const p = Promise.reject(new Error('Oops!'));
try {
await p;
} catch (err) {
console.log('This runs...');
}
try {
return p;
} catch (err) {
console.log('This does NOT run!');
}
There are a few workarounds for this quirk, but one approach we like is using return await
.
try {
return await p;
} catch (err) {
console.log('This runs!');
}
Sometimes you want to call an async function, do something else, and then await
on the async function. Promises are just variables in JavaScript, so you can call an async function, get the promise response, and await
on it later.
const ee = new EventEmitter();
// Execute the function, but don't `await` so we can `setTimeout()`
const p = waitForEvent(ee, 'test');
setTimeout(() => ee.emit('test'), 1000);
// Wait until `ee` emits a 'test' event
await p;
async function waitForEvent(ee, name) {
await new Promise(resolve => {
ee.once(name, resolve);
});
}
await
with Promise Chaining
We recommend using Axios over fetch()
, but in some cases you may need to use fetch()
. And fetch()
famously requires you to asynchronously parse the response body. Here's how you can make a request with fetch()
and parse the response body with 1 await
.
const res = await fetch('/users').then(res => res.json());
Another quirk of fetch()
is that it doesn't throw an error if the server responds with an error code, like 400. Here's how you can make fetch()
throw a catchable error if the response code isn't in the 200 or 300 range.
const res = await fetch('/users').
then(res => {
if (res.status < 200 || res.status >= 400) {
throw new Error('Server responded with status code ' + res.status);
}
return res;
}).
then(res => res.json());
Event emitters are a common pattern in JavaScript, but they don't work well with async/await because they're not promises. Here's how you can await
on an event from a Node.js event emitter.
const ee = new EventEmitter();
setTimeout(() => ee.emit('test'), 1000);
// Wait until `ee` emits a 'test' event
await new Promise(resolve => {
ee.once('test', resolve);
});
18