JavaScript - async/await
Overview
Estimated time: 35–45 minutes
Async/await makes asynchronous code read like synchronous code. Learn how to handle errors, run tasks sequentially or concurrently, and manage timeouts and cancellation.
Learning Objectives
- Refactor promise chains into clear
async
/await
flows. - Use
try/catch/finally
to handle errors and cleanup. - Run tasks sequentially vs concurrently with
Promise.all
. - Implement timeouts and cancellation with
Promise.race
andAbortController
.
Prerequisites
- JavaScript - Promises
- JavaScript - Async Iterables (recommended)
Basics
// An async function always returns a Promise
async function getValue() {
return 42; // resolves with 42
}
async function run() {
const v = await getValue();
return v * 2; // resolves with 84
}
run().then(console.log);
Error handling
async function mayFail(flag) {
if (flag) throw new Error('boom');
return 'ok';
}
async function main() {
try {
const res = await mayFail(false);
console.log(res);
} catch (e) {
console.error('caught:', e.message);
} finally {
console.log('cleanup');
}
}
Sequential vs concurrent
const delay = (ms, v) => new Promise(res => setTimeout(() => res(v), ms));
// Sequential (waits each in turn ~ 300ms total)
async function seq() {
const a = await delay(100, 'A');
const b = await delay(100, 'B');
const c = await delay(100, 'C');
return [a, b, c];
}
// Concurrent (~ 100ms total)
async function conc() {
const [a, b, c] = await Promise.all([
delay(100, 'A'),
delay(100, 'B'),
delay(100, 'C'),
]);
return [a, b, c];
}
// Mapping to promises, then await all
async function mapConcurrent(values) {
const promises = values.map((v, i) => delay(50 + i*10, v*v));
return await Promise.all(promises);
}
Timeouts and cancellation
// Timeout with Promise.race
async function withTimeout(promise, ms) {
const timeout = new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), ms));
return Promise.race([promise, timeout]);
}
// fetch + AbortController (browser environments)
async function fetchWithAbort(url, ms = 5000) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), ms);
try {
const res = await fetch(url, { signal: ctrl.signal });
return await res.text();
} finally {
clearTimeout(timer);
}
}
Top-level await
Top-level await
works inside ES modules (<script type="module">
) or modern build setups. Otherwise, wrap code in an async IIFE: (async () => { /* await ... */ })()
.
Common Pitfalls
- Accidental serialization:
await
in a loop runs tasks sequentially. For parallelism, build an array of promises andawait Promise.all
. - Missing await: Forgetting
await
yields a pending promise, not the value. - Try/catch scope: Only errors thrown/awaited within the
try
are caught.
Try it
Run to compare sequential vs concurrent, and handle errors with try/catch: