Skip to content
Laviyx Learn

Try

JavaScript course menu

JavaScript async and await: Writing Asynchronous Code That Reads Like Synchronous Code

Learn async and await in JavaScript: async functions return promises, await pauses until a promise settles, error handling with try/catch/finally, sequential versus parallel awaits with Promise.all, top-level await, await in loops, and common mistakes.

Intermediate8 min readUpdated
On this page

async and await are a nicer way to work with promises. They let you write asynchronous code that reads top to bottom, like normal synchronous code, while still not blocking the page.

The same code, two ways#

This lesson uses a small simulated "request" with a timer:

JavaScript
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));

// With .then()
function showUserThen() {
  return wait(20, { name: "Asha" }).then((user) => {
    console.log("then:", user.name);
  });
}

// With async/await
async function showUserAwait() {
  const user = await wait(20, { name: "Asha" });
  console.log("await:", user.name);
}

await showUserThen();
await showUserAwait();
Output
then: Asha
await: Asha
  • async before a function makes it an async function.
  • Inside it, await promise pauses that function until the promise settles, then gives you its value.
  • While it is paused, the rest of the program (and the page) keeps running.

(The last two lines use await at the top level of the file, which is allowed in modules; see below.)

async functions return promises#

An async function always returns a promise, even if you return a plain value:

JavaScript
async function getNumber() {
  return 42;
}

const result = getNumber();
console.log(result instanceof Promise);
console.log(await result);
Output
true
42

So callers either await it (inside another async function) or use .then().

Handling errors with try...catch#

When an awaited promise rejects, await throws the error, so you handle it with ordinary try...catch:

JavaScript
function getUser(id) {
  return new Promise((resolve, reject) =>
    setTimeout(() => (id === 1 ? resolve({ name: "Asha" }) : reject(new Error(`User ${id} not found`))), 20)
  );
}

async function showUser(id) {
  try {
    const user = await getUser(id);
    console.log("Hello,", user.name);
  } catch (error) {
    console.log("Could not load user:", error.message);
  } finally {
    console.log(`(request for user ${id} done)`);
  }
}

await showUser(1);
await showUser(7);
Output
Hello, Asha
(request for user 1 done)
Could not load user: User 7 not found
(request for user 7 done)

Errors from async code are handled exactly like synchronous ones, which is one of the biggest benefits of await.

Sequential versus parallel#

Each await waits before the next line starts. That is right when a step depends on the previous one, but wasteful when the steps are independent:

JavaScript
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));

let start = Date.now();
const a = await wait(100, "profile");
const b = await wait(100, "orders");
console.log("sequential took about", Math.round((Date.now() - start) / 100) * 100, "ms");

start = Date.now();
const [c, d] = await Promise.all([wait(100, "profile"), wait(100, "orders")]);
console.log("parallel took about", Math.round((Date.now() - start) / 100) * 100, "ms");
console.log(a, b, c, d);
Output
sequential took about 200 ms
parallel took about 100 ms
profile orders profile orders

Start independent work together and await Promise.all([...]) the results.

Loops#

To process items one after another, use await inside a for...of loop:

JavaScript
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));

for (const step of ["upload", "resize", "publish"]) {
  const done = await wait(10, step);
  console.log("done:", done);
}
Output
done: upload
done: resize
done: publish

forEach does not wait for async callbacks: items.forEach(async (item) => { await ... }) starts them all at once and moves on immediately. Use for...of for one-by-one, or Promise.all(items.map(async (item) => ...)) for all-at-once.

Top-level await#

In modules (files loaded with type="module" in browsers, .mjs files or "type": "module" projects in Node.js), you can use await outside any function, as the examples on this page do. In classic scripts, wrap the code in an async function instead:

JavaScript
async function main() {
  const data = await loadData();
  console.log(data);
}
main();

Common mistakes#

Practice#

Practice exercise

Using the wait helper, write an async function loadDashboard() that loads "user" (30 ms), then, in parallel, "orders" (40 ms) and "messages" (20 ms), and returns an object with all three. Print the result.

Show solution
JavaScript
const wait = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms));

async function loadDashboard() {
  const user = await wait(30, "user");
  const [orders, messages] = await Promise.all([wait(40, "orders"), wait(20, "messages")]);
  return { user, orders, messages };
}

console.log(await loadDashboard());
Output
{ user: 'user', orders: 'orders', messages: 'messages' }

Key takeaway#

Finished this lesson?