← All JavaScript lessons

JavaScript / Chapter 05

Promises, APIs & Error Handling

Understand asynchronous execution, compose promises, fetch remote data, cancel stale requests, and present loading, success, empty, and error states.

Chapter roadmap

Understand asynchronous execution, compose promises, fetch remote data, cancel stale requests, and present loading, success, empty, and error states. Work through the concepts in order, type every example yourself, and keep a short debugging log that records predictions, errors, fixes, and what each fix taught you.

Learning objectives

  • Explain asynchronous execution
  • Use promises and async/await
  • Fetch and validate HTTP responses
  • Design resilient UI states

05.1

Asynchronous execution and promises

A promise represents a future completion or failure. then transforms a fulfilled value, catch handles rejection, and finally performs cleanup. Promise chains should return their next promise so errors and results flow through one predictable path.

Before running the example, trace each expression and write down the expected state or result. Then run it, compare the actual result, and change one input or rule to confirm that you understand which part controls the behavior.

Example
wait(500)
  .then(() => "Ready")
  .then(message => console.log(message))
  .catch(error => console.error(error))
  .finally(() => console.log("Finished"));
Result
Ready
Finished
Check your understanding

Explain the example in plain language, identify one boundary or failure case, and revise it so the output changes in a predictable way.

05.2

async and await

An async function always returns a promise. await pauses that function—not the whole program—until the promise settles. try/catch gives asynchronous code familiar error handling, and finally is useful for clearing loading indicators.

Before running the example, trace each expression and write down the expected state or result. Then run it, compare the actual result, and change one input or rule to confirm that you understand which part controls the behavior.

Example
async function loadProfile() {
  setLoading(true);
  try {
    const profile = await getProfile();
    renderProfile(profile);
  } catch (error) {
    showError(error.message);
  } finally {
    setLoading(false);
  }
}
Result
The interface displays loading, then either profile data or a readable error.
Check your understanding

Explain the example in plain language, identify one boundary or failure case, and revise it so the output changes in a predictable way.

05.3

fetch and HTTP responses

fetch rejects for network failures but not automatically for HTTP error statuses. Check response.ok before reading the body. JSON parsing is asynchronous, and returned data should be validated before the interface assumes properties exist.

Before running the example, trace each expression and write down the expected state or result. Then run it, compare the actual result, and change one input or rule to confirm that you understand which part controls the behavior.

Example
async function fetchPosts() {
  const response = await fetch("https://example.test/api/posts");
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }
  const data = await response.json();
  if (!Array.isArray(data)) throw new TypeError("Expected an array");
  return data;
}
Result
A valid response returns posts; network, HTTP, and shape failures become explicit errors.
Check your understanding

Explain the example in plain language, identify one boundary or failure case, and revise it so the output changes in a predictable way.

05.4

Concurrency and cancellation

Promise.all runs independent operations concurrently and fails when one rejects; Promise.allSettled reports every result. AbortController can cancel a fetch when a new search supersedes it or a component is removed. Prevent stale responses from overwriting newer state.

Before running the example, trace each expression and write down the expected state or result. Then run it, compare the actual result, and change one input or rule to confirm that you understand which part controls the behavior.

Example
let controller;
async function search(query) {
  controller?.abort();
  controller = new AbortController();
  const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
    signal: controller.signal
  });
  return response.json();
}
Result
Starting a new search cancels the previous request.
Check your understanding

Explain the example in plain language, identify one boundary or failure case, and revise it so the output changes in a predictable way.

Chapter assignments

Complete these in order. Later assignments assume that earlier skills are working. Do not only test the sample values; design tests that challenge boundaries, missing values, incorrect types, empty collections, and other likely failures.

  1. A5.1

    Create promise-based delay, timeout, and retry helpers with clear error behavior.

    Submit readable source code, a brief design note, and evidence from at least three tests including one boundary or invalid case.

  2. A5.2

    Fetch a public-style mock endpoint and render loading, success, empty, and failure states.

    Submit readable source code, a brief design note, and evidence from at least three tests including one boundary or invalid case.

  3. A5.3

    Run three independent requests concurrently and display partial results using Promise.allSettled.

    Submit readable source code, a brief design note, and evidence from at least three tests including one boundary or invalid case.

  4. A5.4

    Build a debounced search interface that cancels stale requests and ignores out-of-order responses.

    Submit readable source code, a brief design note, and evidence from at least three tests including one boundary or invalid case.

Chapter project

Data Explorer

Data Explorer — Build an interface that fetches records, supports search and filters, handles loading/empty/error states, caches successful responses in memory, and offers retry. Validate response data before rendering and cancel superseded searches.

Required process

  1. Write a short specification listing inputs, outputs, rules, and failure cases.
  2. Break the work into small functions, queries, modules, or classes appropriate to the language.
  3. Build the smallest working version before adding optional features.
  4. Test normal, boundary, empty, and invalid cases and record the results.
  5. Refactor names and duplication, then write a concise user guide.

Key terms

  • asynchronous
  • promise
  • fulfillment
  • rejection
  • async
  • await
  • HTTP status
  • JSON
  • concurrency
  • cancellation