JavaScriptLesson 16 of 18

Promises and Async JavaScript

Some work takes time: loading a file, calling an API, waiting for a timer. JavaScript does not freeze while it waits. It schedules the work and keeps running, and a promise is the object that represents that not-yet-finished job. Promises are the modern way to handle tasks that finish later.

Some work takes time: loading a file, calling an API, waiting for a timer. JavaScript does not freeze while it waits. It schedules the work and keeps running, and a promise is the object that represents that not-yet-finished job. Promises are the modern way to handle tasks that finish later.

A promise starts as pending, then settles one way or another: fulfilled if the work succeeded, or rejected if it failed. The then method runs when a promise fulfills, and catch runs when it rejects. This is the core pattern: start a job, say what to do when it finishes, and say what to do if it breaks.

The newest and easiest way to use promises is async await. Mark a function with async and you can await a promise inside it. Await pauses that one function until the promise settles, without blocking the rest of the page. The code reads top to bottom like ordinary JavaScript, which is why async await is the style you see in modern codebases.

From callbacks to promises

Older code passed a function to run when a job finished, and nested those functions for multiple steps. Promises flatten the same logic: each then is a step in a chain, and the code reads as a sequence instead of nested blocks. Chaining is the whole point of promises.

JS
fetch("/api/user")
  .then((res) => res.json())
  .then((user) => loadProfile(user))
  .catch((err) => showError(err));

async await reads like a story

With async await, the same chain becomes straight lines of code. Await hands back the finished value, and you store it in a variable like any other result. Wrap the block in try catch to handle failures the same way you handle any error.

JS
async function loadProfile() {
  try {
    const res = await fetch("/api/user");
    const user = await res.json();
    renderProfile(user);
  } catch (err) {
    showError(err);
  }
}

Promise.all runs jobs side by side

Sometimes several jobs can run at once, like loading a user and a list of orders. Promise.all takes an array of promises and waits for all of them. The result is an array of values in the same order you passed the promises, and it rejects if any single one fails.

JS
const userPromise = fetch("/api/user").then((r) => r.json());
const ordersPromise = fetch("/api/orders").then((r) => r.json());

const [user, orders] = await Promise.all([userPromise, ordersPromise]);

Syntax

JS
async function getWeather(city) {
  try {
    const res = await fetch("/api/weather?city=" + city);
    const data = await res.json();
    return data.temp + " degrees";
  } catch (err) {
    return "Weather unavailable";
  }
}

Floating Promise Without catch Silently Fails

Unhandled promise rejection is silent in some browsers — always add .catch.

JS
fetch(url).catch(console.error)

Good to know

  • A promise represents work that has not finished yet.
  • then runs on success, catch runs on failure, finally runs after both.
  • async await makes promise code read like ordinary lines of code.
  • Promise.all runs several jobs at once and waits for all of them.
  • fetch returns a promise, so it chains with then or awaits.
Try it Yourself Edit the code, press Run, then check what changed in the Result.
Ready
Code
Result

Practice makes it stick

Open the Playground and build your own page with the tags you just learned.

→ Open Playground