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');
});
return await
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!');
}
Delayed await
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());
Waiting for Events
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);
});
Top comments (2)
Nice Article!. IMO
await
with promise chaining is not ideal and should not be used at all. By defaultawait
statement will wait for the execution of the promise, so adding athen
block is not required.The above statement can be simplified with a single
await
within atry/catch
block like followingThe problem is that you need to do
await res.json()
as well, but otherwise your approach works. I think theawait fetch().then(res => res.json())
pattern is common enough that it's worth mentioning.