Async code and the event loop

Why Node does not wait, and how to use promises, async and await.

Node runs your JavaScript on one thread. Slow work, such as reading a file, querying a database or calling another API, is handed off, and Node carries on serving other requests. When the work finishes, your code continues.

Node.js
console.log("1. start");

setTimeout(() => console.log("4. timer"), 0);

Promise.resolve().then(() => console.log("3. promise"));

console.log("2. end of script");
Output
1. start
2. end of script
3. promise
4. timer

Even with 0 ms, the timer runs last. Node finishes the current code first, then promise callbacks, then timers.

async and await

Node.js
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));
const since = (t) => Math.round((Date.now() - t) / 100) * 100;

let t = Date.now();
const user = await wait(300, "Kavin");
const jobs = await wait(300, 5);
console.log("one by one:", since(t), "ms");

t = Date.now();
const [u, j] = await Promise.all([wait(300, "Kavin"), wait(300, 5)]);
console.log("together:", since(t), "ms");
console.log(user, jobs, u, j);
Output
one by one: 600 ms
together: 300 ms
Kavin 5 Kavin 5

Two awaits in a row wait for each other. When two requests do not depend on each other, start them together with Promise.all and the page loads in half the time.

Handle failures

Node.js
try {
  const res = await fetch("https://api.example.com/jobs");
  if (!res.ok) throw new Error(`API answered ${res.status}`);
  const jobs = await res.json();
  console.log(jobs.length);
} catch (err) {
  console.error("Could not load jobs:", err.message);
}

An awaited promise that fails throws an error, so wrap it in try/catch. A failed promise that nobody catches can stop the whole Node process.

  • Never run a long loop or a Sync file function inside a request handler. While it runs, every other user waits.
  • Use await only inside async functions, or at the top level of an ES module.
โ† Getting started with Node.jsModules and files โ†’