JavaScript promises, async, and await
Write asynchronous code that reads like synchronous code, and handle errors with try and catch.
On this page
JavaScript callbacks and arrow functions.
- Write asynchronous code that reads like synchronous code, and handle errors with try and catch.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A promise represents a value that will be available later. It is pending, then either fulfilled with a value or rejected with an error. An async function always returns a promise, and await pauses that function until a promise settles, without blocking the rest of the program. Code after the call to an async function runs before the awaited work finishes.
Promise.all starts several promises at once and resolves when all are done, so independent tasks overlap and the total time is that of the slowest. Promise.race settles as soon as the first one does. A rejected promise throws at the await, so an ordinary try/catch handles it. Awaiting independent tasks one after the other, instead of together, is a common performance mistake.
Code example
function delay(value, ms) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
async function main() {
console.log("start");
const a = await delay("A", 20);
console.log("got " + a);
const [b, c] = await Promise.all([delay("B", 30), delay("C", 10)]);
console.log("got " + b + c);
try {
await Promise.reject(new Error("boom"));
} catch (error) {
console.log("caught " + error.message);
}
const winner = await Promise.race([delay("slow", 60), delay("fast", 5)]);
console.log("race winner " + winner);
}
main().then(() => console.log("done"));
console.log("after main() call");start after main() call got A got BC caught boom race winner fast done
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
function delay(value, ms) { return new Promise((resolve) => setTimeout(() => resolve(value), ms));} async function main() { console.log("start"); const a = await delay("A", 20); console.log("got " + a); const [b, c] = await Promise.all([delay("B", 30), delay("C", 10)]); console.log("got " + b + c); try { await Promise.reject(new Error("boom")); } catch (error) { console.log("caught " + error.message); } const winner = await Promise.race([delay("slow", 60), delay("fast", 5)]); console.log("race winner " + winner);} main().then(() => console.log("done"));console.log("after main() call");Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| main runs synchronously until its first await. | — | start | |
| main is suspended here for 20 ms, and control returns to the caller. | — | start | |
| The code after main() runs while main is waiting. | — | start after main() call | |
| After 20 ms main resumes. | — | start after main() call got A | |
| Both timers run together, so this takes about 30 ms, not 40. | — | start after main() call got A | |
| Results keep the order of the input array, even though C finished first. | — | start after main() call got A got BC | |
| A rejected promise throws at await, and catch handles it. | — | start after main() call got A got BC caught boom | |
| The 5 ms promise settles first and wins. | — | start after main() call got A got BC caught boom race winner fast | |
| main returns a promise; then runs when it finishes. | — | start after main() call got A got BC caught boom race winner fast done |
Behind the scenes
How JavaScript processes this code
A JavaScript engine parses and runs the source. A function body runs when the function is called. console.log writes to the console.
Cost and common mistakes
Time is that of the awaited operations; Promise.all of independent tasks takes about the longest one.
- Forgetting await gives you a promise object instead of its value, and a rejection nobody handles can crash the process.
- await inside a forEach callback does not wait for the loop. Use for...of or Promise.all with map.
- Timers are not exact. The sample uses large gaps between delays so that the order is stable.
Try it yourself
How long does await delay("x", 20); await delay("y", 30); take compared with Promise.all?
Reveal answer and reasoning
About 50 ms when awaited one after the other, against about 30 ms with Promise.all, since the delays overlap.