TypeScript

Promises and async

Work that finishes later, and the try/catch that never catches.

After this lesson you can

  • Await a promise and handle its rejection
  • Say why a floating promise escapes try/catch
  • Run independent work in parallel rather than in sequence

A promise is a value that is not ready yet. await waits for it.

async function load(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json() as Promise<User>;
}

An async function always returns a promise, whatever you write inside it. Returning 5 returns Promise<number>.

The try/catch that never catches

try {
  sendWelcomeEmail(user.email);   // no await
} catch (err) {
  logger.warn(err);               // never runs
}

try guards the synchronous execution of its block. The call returns a promise immediately, the block finishes, and the rejection arrives later with nothing on the stack to catch it — as an unhandled rejection, which under Node's default policy takes the process down.

The fix is await. Every promise should be awaited, returned, or explicitly discarded with a comment saying why it is fire-and-forget. A bare promise used as a statement always deserves a second look.

Sequential against parallel

const a = await loadUser(id);     // waits
const b = await loadOrders(id);   // then waits again

Those two do not depend on each other, so waiting twice wastes the first wait. Promise.all starts both and waits once:

const [a, b] = await Promise.all([loadUser(id), loadOrders(id)]);

Fifty sequential calls at 500 microseconds each is 25 milliseconds of pure waiting. In parallel it is closer to one round trip.

Promise.all rejects as soon as any one rejects. When you want every result including the failures, Promise.allSettled gives you both.

Try it

Waiting once instead of three timestypescript-5
function slow<T>(value: T, ms: number): Promise<T> {  return new Promise((resolve) => setTimeout(() => resolve(value), ms));} export async function run(): Promise<Record<string, number>> {  const sequentialStart = Date.now();  await slow("a", 60);  await slow("b", 60);  await slow("c", 60);  const sequential = Date.now() - sequentialStart;   const parallelStart = Date.now();  await Promise.all([slow("a", 60), slow("b", 60), slow("c", 60)]);  const parallel = Date.now() - parallelStart;   // Rounded to tens of milliseconds so the result is stable to look at.  return {    sequentialMs: Math.round(sequential / 10) * 10,    parallelMs: Math.round(parallel / 10) * 10,  };}
What to look for

Try it yourself

2 visible tests · 2 hidden tests

wait(ms) is given: it resolves to ms after ms milliseconds. Implement sumDelays(delays: number[]): Promise<number>, which awaits every one of them and returns the sum. However you write the awaiting, the sum must come out right — this sandbox checks the answer, not how long it took.

  • sumDelays([5,10,15])
  • sumDelays([])
Loading editor…

Sign up to check the hidden tests and save your progress. Sign up