Async/await resources
Table of contents
Part of “Learn to write async code in JavaScript.”
Introductory articles
- This is an introductory post to async/await by David Walsh that explains the basics and has some usage tips.
- MDN article on async functions. Running the examples helped me understand what’s going on. In the examples you can also find some usage tips—they elaborate on them after the code snippets.
Jake Archibald on async functions
Async functions - making promises friendly.
Notes from Jake’s article
“Anything you await is passed through
Promise.resolve()
, so you can safely await non-native promises.” Example: Logging a fetch.Async functions always return a promise. For example, if you return a string, you get back a promise that fulfills with that string. Async return values
If you pass an async function to
.map
, and you await a promise inside, it won’t wait every promise to resolve, but instead, will run all of them in parallel. By the way—I say that, not Jake—the same is true forforEach
loops. If you want to wait for each separate promise to resolve in an iteration, you can use a for-of loop. You can find more on await in loops in this StackOverflow thread.Avoid going too sequential. Prefer the following code:
async function logInOrder(urls) { // fetch all the URLs in parallel const textPromises = urls.map(async (url) => { const response = await fetch(url); return response.text(); }); // log them in sequence for (const textPromise of textPromises) { console.log(await textPromise); } }
Over this code:
// NOT reccomended. async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } }
By the way, the first snippet is a replacement for the map + reduce pattern from the promises page—and it’s way more readable.
My observations
The lines after a statement with
await
can be viewed as the code inside athen
callback. That code executes asynchronously as a micro-task. Each consecutiveawait
after the first can be viewed as anotherthen
in the promise chain. Consider this example:(async () => { const result = await Promise.resolve(3); console.log(result); const double = await Promise.resolve(result * 2); console.log(double); })(); (async () => { console.log("Synchronous"); const result = await Promise.resolve(4); console.log(result); })();
The code above prints:
/** * - Synchronous * - 3 * - 4 * - 6 */
The equivalent code with promises is the following:
Promise.resolve(3) .then((res) => { console.log(res); return res * 2; }) .then((double) => console.log(double)); console.log("Synchronous"); Promise.resolve(4).then((res) => console.log(res));
Error handling in async/await when we have a nested function that uses a promise: As long as every promise is awaited, the root
catch
will catch all the nested errors. If you don’t await a promise—a use case is to start parallel requests—you won’t catch the error in the parent. You’ll have to try-catch inside the async function you’re not awaiting. Or use a pattern like thePromise.all(array of promises)
or the map + for-of loop you saw earlier. Some examples that illustrate that point:
const failingPromise = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(Error("Promise failed."));
});
});
};
const nestedAwait = async () => {
await failingPromise();
return 2;
};
// 1. Parent catch works.
(async () => {
try {
await nestedAwait();
} catch (err) {
console.log(err);
}
})();
// // 2. It also works without nesting.
// (async () => {
// try {
// await failingPromise();
// } catch (err) {
// console.log(err);
// }
// })();
// // 3. Doesn't work, it throws an UnhandledPromiseRejectionWarning.
// const nestedDiscardThePromise = async () => {
// failingPromise();
// return 2;
// };
// (async () => {
// try {
// await nestedDiscardThePromise();
// } catch (err) {
// console.log(err);
// }
// })();
// // 4. A nested map also discards the promises without giving
// // the chance to catch them.
// const nestedMapDiscardsPromises = async () => {
// const data = [1, 2, 3];
// const promises = data.map(async item => {
// await failingPromise();
// });
// return promises;
// };
// (async () => {
// try {
// await nestedMapDiscardsPromises();
// } catch (err) {
// console.log(err);
// }
// })();
// 5. But with map and for-of we can catch the errors (the first at least).
// const nestedMapForOf = async () => {
// const data = [1, 2, 3];
// const promises = data.map(async item => {
// await failingPromise();
// });
// // eslint-disable-next-line no-restricted-syntax
// for (const promise of promises) {
// await promise;
// }
// return 2;
// };
// (async () => {
// try {
// await nestedMapForOf();
// } catch (err) {
// console.log(err);
// }
// })();
Other links
- Robin Wieruch - How to fetch data with React Hooks.
- Dr. Axel Rauschmayer’s articles tagged with async. They are more advanced than the previous resources, and I didn’t have the time to read them and keep notes yet.
- The only case where you want to
return await promise;
instead of justreturn promise
, inside anasync
function, is if you return inside a try-catch block. In the first case you catch the error and in the second you don’t. await vs return vs return await.
Other things to read
Popular
- Reveal animations on scroll with react-spring
- Gatsby background image example
- Extremely fast loading with Gatsby and self-hosted fonts