JavaScript Promises vs Async/Await: Master Async Code (Complete Guide)
The Problem You're Solving
Your code processes data sequentially (slow):
// ā Slow - Waits 3 seconds total (1+1+1)
const user = getUser(1); // 1 second
const posts = getPosts(1); // 1 second
const comments = getComments(1); // 1 second
// Total: 3 seconds (serial)
// ā
Fast - Runs in parallel (1 second)
const [user, posts, comments] = await Promise.all([
getUser(1),
getPosts(1),
getComments(1)
]);
// Total: 1 second (parallel)
That difference = 3-second page load vs 1-second page load = 65% faster.
Async mastery appears in 29% of JavaScript interviews and directly impacts app responsiveness.
Promises: Foundation
Problem: Callback Hell
// ā Hard to read
getUser(1, function(err, user) {
if (err) throw err;
getPosts(user.id, function(err, posts) {
if (err) throw err;
getComments(posts[0].id, function(err, comments) {
if (err) throw err;
console.log(comments);
});
});
});
Solution: Promises
// ā
Readable
getUser(1)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => console.log(comments))
.catch(err => console.error(err));
Promise States
const promise = new Promise((resolve, reject) => {
if (success) {
resolve(value); // ā
Fulfilled
} else {
reject(error); // ā Rejected
}
// Pending until resolved or rejected
});
// States:
// Pending ā (do work) ā Fulfilled/Rejected (final)
const p1 = new Promise(resolve => resolve('done'));
console.log(p1); // Promise { 'done' } (fulfilled)
const p2 = new Promise((resolve, reject) => reject('error'));
console.log(p2); // Promise { <rejected> 'error' }
Chaining: .then()
fetch('/api/user')
.then(res => res.json()) // Chain operation 1
.then(user => getPosts(user.id)) // Chain operation 2
.then(posts => console.log(posts)) // Chain operation 3
.catch(err => console.error(err)); // Error handling
Parallel: Promise.all()
// ā Sequential (3 seconds)
const user = await getUser(1);
const posts = await getPosts(1);
const comments = await getComments(1);
// ā
Parallel (1 second)
const [user, posts, comments] = await Promise.all([
getUser(1),
getPosts(1),
getComments(1)
]);
// If ANY promise rejects, whole thing fails
Race: Promise.race()
// ā
Fastest request wins
const result = await Promise.race([
fetch('api1.example.com'),
fetch('api2.example.com'),
fetch('api3.example.com')
]);
// Whichever finishes first
// Use case: Timeout
const timeoutPromise = new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('timeout')), 5000)
);
const result = await Promise.race([
fetch('/slow-api'),
timeoutPromise
]);
Async/Await: Cleaner Syntax
Basic Async/Await
// ā Promise chain
function loadUser(id) {
return fetch(`/api/users/${id}`)
.then(res => res.json())
.then(user => console.log(user));
}
// ā
Async/await
async function loadUser(id) {
const res = await fetch(`/api/users/${id}`);
const user = await res.json();
console.log(user);
}
// Call it
await loadUser(1); // Wait for completion
Error Handling
// ā Promise - .catch() required
getUser(1)
.then(user => getPosts(user.id))
.catch(err => console.error(err));
// ā
Async/await - try/catch (familiar pattern)
async function loadUserPosts() {
try {
const user = await getUser(1);
const posts = await getPosts(user.id);
return posts;
} catch (err) {
console.error(err);
return [];
}
}
Parallel Operations
// ā Sequential (slow)
async function loadAll() {
const user = await getUser(1);
const posts = await getPosts(1);
const comments = await getComments(1);
return [user, posts, comments];
}
// ā
Parallel (fast)
async function loadAll() {
const [user, posts, comments] = await Promise.all([
getUser(1),
getPosts(1),
getComments(1)
]);
return [user, posts, comments];
}
Real-World Examples
Example 1: Form Submission
// ā Callback hell
function handleSubmit(formData) {
validateForm(formData, function(err, result) {
if (err) {
showError(err);
return;
}
saveToDatabase(result, function(err, id) {
if (err) {
showError(err);
return;
}
redirectToDashboard(id);
});
});
}
// ā
Async/await
async function handleSubmit(formData) {
try {
const validated = await validateForm(formData);
const id = await saveToDatabase(validated);
redirectToDashboard(id);
} catch (err) {
showError(err);
}
}
Example 2: Retry Logic
// Retry up to 3 times on failure
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url);
} catch (err) {
if (i === retries - 1) throw err; // Last attempt failed
await sleep(1000); // Wait 1 second before retry
}
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Usage
const result = await fetchWithRetry('https://api.example.com');
Example 3: Timeout Wrapper
// Wrap any promise with timeout
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('timeout')), ms)
)
]);
}
// Usage
try {
const result = await withTimeout(fetch('/slow-api'), 5000);
} catch (err) {
console.error('Request timed out');
}
Common Mistakes
ā Mistake 1: Mixing Callbacks and Promises
// WRONG - Mixing patterns
setTimeout(() => {
getUser(1).then(user => console.log(user));
}, 1000);
// CORRECT - All async
async function loadWithDelay() {
await sleep(1000);
const user = await getUser(1);
console.log(user);
}
ā Mistake 2: Forgetting await
// WRONG - Returns Promise, not data
async function getUser() {
const res = fetch('/api/user'); // Missing await
return res.json(); // res is Promise, not Response
}
// CORRECT
async function getUser() {
const res = await fetch('/api/user');
return res.json();
}
ā Mistake 3: Sequential When Parallel Intended
// WRONG - 3 seconds (sequential)
async function loadAll() {
const user = await getUser();
const posts = await getPosts();
const comments = await getComments();
}
// CORRECT - 1 second (parallel)
async function loadAll() {
return Promise.all([
getUser(),
getPosts(),
getComments()
]);
}
ā Mistake 4: Forgetting Error Handling
// WRONG - Unhandled promise rejection
async function dangerous() {
const user = await getUser(); // If rejects, error not caught
return user;
}
dangerous(); // Crashes silently
// CORRECT
async function safe() {
try {
const user = await getUser();
return user;
} catch (err) {
console.error(err);
return null;
}
}
Performance Comparison
Sequential (Slow)
async function slow() {
const a = await task1(); // 1s
const b = await task2(); // 1s (waits for a)
const c = await task3(); // 1s (waits for b)
// Total: 3s
}
Parallel (Fast)
async function fast() {
const [a, b, c] = await Promise.all([
task1(), // 1s
task2(), // 1s (runs simultaneously)
task3() // 1s (runs simultaneously)
]);
// Total: 1s
}
FAQ: Async Mastery
Q1: Promise vs Async/Await - Which to use?
A: Async/await for new code. Promises for library code.
// ā
Use async/await - cleaner
async function loadUser() {
const user = await getUser(1);
return user;
}
// ā
Use Promise - library wrapper
function withCache(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
return cache.has(key)
? Promise.resolve(cache.get(key))
: fn(...args).then(result => {
cache.set(key, result);
return result;
});
};
}
Q2: How do I cancel a promise?
A: Use AbortController.
const controller = new AbortController();
fetch('/slow-api', { signal: controller.signal })
.then(res => res.json())
.catch(err => {
if (err.name === 'AbortError') {
console.log('Request cancelled');
}
});
// Cancel
setTimeout(() => controller.abort(), 5000);
Q3: Interview Question: Implement Promise.all() yourself.
A: Here's how:
function allPromises(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
if (promises.length === 0) {
resolve([]);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(result => {
results[index] = result;
completed++;
if (completed === promises.length) {
resolve(results); // All done
}
})
.catch(err => {
reject(err); // First error rejects all
});
});
});
}
// Test
allPromises([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]).then(results => console.log(results)); // [1, 2, 3]
Q4: Async/await vs Promises - Performance?
A: Same performance. Async/await is syntactic sugar.
// These are equivalent
async function fn1() {
return await promise;
}
function fn2() {
return promise.then(x => x);
}
// Both use same Promise machinery under the hood
Conclusion
Master async patterns and your code will be:
- Readable - Async/await looks synchronous
- Correct - Proper error handling prevents crashes
- Fast - Promise.all for parallel operations
- Maintainable - Easy to understand flow
The rule: Use async/await for readability, Promise.all for performance.