Curriculum Series

async/await — syntactic sugar over promises

async/await — syntactic sugar over promises

async/await — syntactic sugar over promises

JavaScript

TL;DR

async/await is a language feature that lets you write async code that reads like synchronous code. Under the hood it's still promises — you're just swapping .then() chains for a top-to-bottom flow that's easier to read and easier to debug.

The readability win

Before async/await, chained .then() calls were the norm. They worked, but once you had a handful of dependent async steps, the code turned into a hard-to-follow staircase.

Fetch a user, then fetch their orders:

Promise chain:

javascript
function getUserOrders(userId) {
return fetch(`/api/users/${userId}`)
.then((response) => response.json())
.then((user) => fetch(`/api/orders/${user.orderId}`))
.then((response) => response.json())
.then((order) => {
console.log(order);
});
}

Same thing with async/await:

javascript
async function getUserOrders(userId) {
const userResponse = await fetch(`/api/users/${userId}`);
const user = await userResponse.json();
const orderResponse = await fetch(
`/api/orders/${user.orderId}`
);
const order = await orderResponse.json();
console.log(order);
}

The second reads top-to-bottom like ordinary code. Each await pauses the function until that promise resolves, then resumes with the result.

What async actually does

Adding async to a function does exactly two things:

  1. The function's return value is always wrapped in a promise. If you return a plain value, it gets Promise.resolve'd for you.
  2. You're allowed to use await inside.
javascript
async function greet() {
return 'Hello';
}
// This is the same as
function greet() {
return Promise.resolve('Hello');
}
greet().then((message) => console.log(message)); // "Hello"

What await actually does

await suspends the current async function until the awaited promise settles, then unwraps its resolved value.

javascript
async function loadData() {
console.log('Start');
const data = await fetch('/api/data'); // pauses here
console.log('Done'); // runs after fetch completes
}

Key point people miss: await only pauses this function. The rest of the program keeps running — that's the whole point of it being async.

Error handling

With promises you chain .catch(). With async/await you use the same try...catch you'd use for sync code:

javascript
async function getUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
} catch (error) {
console.log('Something went wrong:', error);
}
}

Common Pitfalls

The biggest footgun is forgetting to handle errors at all. .then() chains made .catch() a visible part of the shape; with async/await it's easy to just not write a try, at which point a rejected promise becomes an unhandled rejection. Wrap any awaited call that can reasonably fail.

The two classic mistakes

Mistake 1: Serialising work that should run in parallel

javascript
// Slow — each request waits for the previous one
async function getUsers(ids) {
const users = [];
for (const id of ids) {
const user = await fetchUser(id);
users.push(user);
}
return users;
}
// Fast — all requests run at the same time
async function getUsers(ids) {
const users = await Promise.all(
ids.map((id) => fetchUser(id))
);
return users;
}

Five users × 1s each = 5 seconds serially, ~1 second in parallel.

Mistake 2: Forgetting async functions always return promises

javascript
async function getData() {
return 42;
}
// This does not work
const result = getData();
console.log(result); // Promise { 42 }, not 42
// You need to await it or use .then()
const result = await getData(); // inside another async function
console.log(result); // 42

Interview Tip

Show the .then() version first, then the async/await rewrite. It demonstrates that you know what the sugar is sugaring. Finish by mentioning Promise.all — interviewers want to hear that you know when to parallelise.

Why interviewers ask this

Every frontend app talks to APIs. The question checks that you understand async flow, error handling, and the parallel-vs-sequential tradeoff. That's the difference between correct-but-slow code and correct-and-fast code.

Finished reading?

Mark this topic as solved to track your progress.