JavaScript promises, async, and await

Write asynchronous code that reads like synchronous code, and handle errors with try and catch.

JavaScript
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");
Output
start
after main() call
got A
got BC
caught boom
race winner fast
done

Explanation

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.

Explore the dry run
Guided walkthroughReady
1function delay(value, ms) {
2 return new Promise((resolve) => setTimeout(() => resolve(value), ms));
3}
4
5async function main() {
6 console.log("start");
7 const a = await delay("A", 20);
8 console.log("got " + a);
9
10 const [b, c] = await Promise.all([delay("B", 30), delay("C", 10)]);
11 console.log("got " + b + c);
12
13 try {
14 await Promise.reject(new Error("boom"));
15 } catch (error) {
16 console.log("caught " + error.message);
17 }
18
19 const winner = await Promise.race([delay("slow", 60), delay("fast", 5)]);
20 console.log("race winner " + winner);
21}
22
23main().then(() => console.log("done"));
24console.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
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput 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

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.

Further reading