JavaScript Async Await and Fetch

JavaScript async await is a cleaner way to work with promises. An async function always returns a promise, and inside it, await pauses that function until a promise settles, without blocking the rest of the page.

Writing an async Function

Each await waits for the promise before the next line runs, so the code reads top to bottom like synchronous code.

const wait = (ms) => new Promise((r) => setTimeout(r, ms));

async function makeTea() {
  console.log("Boiling water...");
  await wait(300);
  console.log("Steeping...");
  await wait(300);
  return "Tea is ready";
}

makeTea().then(console.log);
Boiling water...
Steeping...
Tea is ready

Fetching JSON from an API

fetch(url) returns a promise for a Response. Check response.ok, then call response.json() to parse the body. This example calls a free public test API.

async function getTodo(id) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

getTodo(1)
  .then((todo) => console.log(todo.id, todo.title, todo.completed))
  .catch((e) => console.log("Request failed:", e.message));
1 delectus aut autem false

Error Handling with try...catch

fetch only rejects on network failure. A 404 or 500 still resolves, so always check res.ok. Wrap awaits in try...catch to handle both cases.

async function load(url) {
  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error("Status " + res.status);
    const data = await res.json();
    console.log("Loaded", data.length, "items");
  } catch (err) {
    console.log("Error:", err.message);
  }
}
load("https://jsonplaceholder.typicode.com/users");
load("https://jsonplaceholder.typicode.com/nothing-here");
Error: Status 404
Loaded 10 items

Sending Data with POST

Pass an options object with method, headers and a JSON string body. Top-level await like this works in modules.

const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ title: "Hello", body: "First post", userId: 1 }),
});
const created = await res.json();
console.log(created.id);
101

Sequential vs Parallel

Awaiting one call after another runs them in sequence. If the calls do not depend on each other, start them together with Promise.all to save time.

const delay = (v) => new Promise((r) => setTimeout(() => r(v), 300));

async function main() {
  const t = performance.now();
  const [a, b, c] = await Promise.all([delay(1), delay(2), delay(3)]);
  console.log(a + b + c, "in about", Math.round((performance.now() - t) / 100) * 100, "ms");
}
main();
6 in about 300 ms

Frequently asked questions

Can I use await outside an async function?

Only at the top level of an ES module (top-level await). In regular scripts and normal functions, await must be inside an async function.

Why does fetch not throw on a 404 error?

fetch treats any HTTP response as success. Check response.ok or response.status yourself and throw if needed.

Is async await faster than promises?

No. It is the same mechanism with nicer syntax. Speed depends on whether you run tasks sequentially or in parallel.