Async/await is syntactic sugar built on top of Promises, introduced in ES2017. It lets you write asynchronous code that looks and reads like synchronous code "” no more chaining .then() calls. Under the hood, an async function always returns a Promise, and await pauses execution inside that function until the awaited Promise settles.
// async function always returns a Promise
async function greet() {
return 'Hello!';
}
greet().then(msg => console.log(msg)); // Hello!
// await pauses until the Promise resolves
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
console.log(user.name);
}
fetchUser(1);
Both approaches do the same thing "” async/await is simply easier to read, especially when you have multiple sequential async steps.
// ── Promise chain ──
function loadData() {
return fetch('/api/user/1')
.then(r => r.json())
.then(user => fetch(`/api/posts?userId=${user.id}`))
.then(r => r.json())
.then(posts => console.log(posts));
}
// ── Async / Await (same logic, cleaner) ──
async function loadData() {
const userRes = await fetch('/api/user/1');
const user = await userRes.json();
const postsRes = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsRes.json();
console.log(posts);
}
Use try/catch to handle errors in async functions. Any rejected promise inside the try block will throw and be caught by catch. You can also use finally for cleanup.
async function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
const user = await res.json();
console.log('User:', user.name);
return user;
} catch (error) {
console.error('Failed to load user:', error.message);
} finally {
console.log('Request finished'); // always runs
}
}
loadUser(42);
Using await sequentially means each operation waits for the previous one to finish. When operations are independent, use Promise.all() with await to run them in parallel and save time.
// Sequential "” total time = time1 + time2
async function sequential() {
const users = await fetch('/api/users').then(r => r.json());
const posts = await fetch('/api/posts').then(r => r.json());
return { users, posts };
}
// Parallel "” total time = max(time1, time2)
async function parallel() {
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
return { users, posts };
}
Be careful when using await inside loops. Using await in a for loop runs iterations sequentially. To run all iterations in parallel, collect the promises first and use Promise.all().
const ids = [1, 2, 3, 4, 5];
// Sequential "” each waits for the previous
async function loadSequential() {
for (const id of ids) {
const user = await fetchUser(id); // waits each time
console.log(user.name);
}
}
// Parallel "” all fire at once
async function loadParallel() {
const promises = ids.map(id => fetchUser(id));
const users = await Promise.all(promises);
users.forEach(u => console.log(u.name));
}
// Note: forEach does NOT work with await "” use for...of or map
// ids.forEach(async id => { ... }); // won't wait correctly
Explore 500+ free tutorials across 20+ languages and frameworks.