Phase 4 · Async, Data & ToolingModule 26~48 min read

Promises & async/await

Coordinate asynchronous work with promises, async functions, composition, and robust error handling.

What you'll learn

Promises represent future completion. async/await makes dependent steps readable, while promise combinators express concurrency, tolerance, racing, and aggregate failure.

By the end of this lesson, you'll be able to:

  • Chain and await promises
  • Select the correct promise combinator
  • Cancel cooperative work with AbortController

Core mental model

Use this decision table as a compact reference. Focus on what each tool means and when it earns its place in production code.

ConceptWhat it meansDecision rule
PromiseEventually fulfilled or rejected resultReturn it so callers own completion
async/awaitSyntax for promise-based sequencingUse try/catch at the boundary that can recover
CombinatorCoordinates multiple promisesChoose all, allSettled, race, or any from failure semantics

Professional workflow

Build the behavior in small, observable steps. Each step should leave something you can inspect or test.

  1. Describe the promise operation boundary: inputs, outputs, state, timing, and expected failures.
  2. Implement the smallest correct path with names that expose intent.
  3. Add edge cases and failure handling before introducing abstractions.
  4. Verify behavior with realistic data and one deliberately adversarial example.
  5. Refactor only after the observable behavior is protected.

Make behavior observable

Before optimizing or abstracting, make inputs, outputs, state changes, timing, and failure paths visible. JavaScript becomes much easier to reason about when hidden work is exposed.

Guided code lab

Run independent work concurrently

Start both operations before awaiting their combined result.

parallel.js
async function loadDashboard() {
  const profilePromise = loadProfile();
  const coursesPromise = loadCourses();
  const [profile, courses] = await Promise.all([profilePromise, coursesPromise]);
  return { profile, courses };
}

Add cooperative cancellation

The caller owns the controller and the operation observes its signal.

cancel.js
async function loadJson(url, signal) {
  const response = await fetch(url, { signal });
  if (!response.ok) throw new Error("HTTP " + response.status);
  return response.json();
}
const controller = new AbortController();
loadJson("/api/course", controller.signal).catch(error => {
  if (error.name !== "AbortError") console.error(error);
});
controller.abort();

Production practice

Contract

Document the promise operation inputs, completion signal, failure channel, ordering, and cancellation behavior.

Verification

Test success, expected failure, timeout or cancellation, empty data, and out-of-order completion.

Operations

Expose duration and failure context without logging secrets or overwhelming the main thread.

Common failure mode

Awaiting independent operations one after another adds their latency; start them together when order is unnecessary.

Independent workshop

Build a resilient dashboard loader with required and optional resources.

Your finished workshop must include:

  • Promise.all for required data
  • allSettled for optional panels
  • Cancellation and typed error messages

Definition of done

Demonstrate the happy path and at least two edge cases, keep responsibilities separated, and add a short note explaining one design choice.

Recap & quick check

Key takeaways

  • Promises represent one future result
  • async functions always return promises
  • Concurrency starts before await
  • Cancellation must be designed into APIs

Quick check

1. What does an async function return?

2. Which combinator waits for every outcome without failing fast?

3. Who should usually own cancellation?

Keep the workshop. Later modules deliberately build on these decisions, so each exercise can become part of your final portfolio architecture.