JavaScript Promises Tutorial - Section 4: Async/Await
Operations run one after another, each waiting for the previous to complete.
Sequential (Slow):
async function sequential() {
const user = await fetchUser(1); // Wait 1s
const posts = await fetchPosts(1); // Wait 1s
const comments = await fetchComments(1); // Wait 1s
return { user, posts, comments };
}
// Total time: 3 seconds
Operations run simultaneously, all starting at once.
Parallel (Fast):
async function parallel() {
const [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);
return { user, posts, comments };
}
// Total time: 1 second (fastest operation)
// Measure sequential
console.time('Sequential');
await sequential();
console.timeEnd('Sequential'); // ~3000ms
// Measure parallel
console.time('Parallel');
await parallel();
console.timeEnd('Parallel'); // ~1000ms
// 3x faster! 🚀
async function processOrder(orderId) {
// Must be sequential - each depends on previous
const order = await fetchOrder(orderId);
const validated = await validateOrder(order);
const payment = await processPayment(validated);
const confirmation = await sendConfirmation(payment);
return confirmation;
}
async function processItemsSequentially(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
await delay(100); // Rate limit: 10 per second
}
return results;
}
async function processLargeFiles(files) {
// Process one at a time to avoid memory issues
for (const file of files) {
await processFile(file);
// File is processed and memory freed before next
}
}
async function loadDashboard(userId) {
// All independent - can run in parallel
const [user, notifications, settings, activity] = await Promise.all([
fetchUser(userId),
fetchNotifications(userId),
fetchSettings(userId),
fetchActivity(userId)
]);
return { user, notifications, settings, activity };
}
async function fetchMultipleUsers(userIds) {
return await Promise.all(
userIds.map(id => fetchUser(id))
);
}
async function preloadResources() {
await Promise.all([
loadImages(),
loadFonts(),
loadScripts(),
fetchInitialData()
]);
}
| Aspect | Sequential | Parallel |
|---|---|---|
| Speed | Slower (sum of all times) | Faster (longest operation) |
| Resource Usage | Lower (one at a time) | Higher (all at once) |
| Dependencies | Handles dependencies | Operations must be independent |
| Error Handling | Stops at first error | All run, fails if any fails |
| Use Case | Dependent operations | Independent operations |
async function getUserProfile(userId) {
// Step 1: Fetch user (required first)
const user = await fetchUser(userId);
// Step 2: Fetch related data in parallel
const [posts, followers, following] = await Promise.all([
fetchPosts(user.id),
fetchFollowers(user.id),
fetchFollowing(user.id)
]);
return { user, posts, followers, following };
}
async function processBatches(items, batchSize = 5) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
// Process batch in parallel
const batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
}
return results;
}
// Process 100 items in batches of 10
await processBatches(items, 10);
async function fetchWithFallback() {
try {
// Try primary and backup in parallel
return await Promise.race([
fetchFromPrimary(),
delay(2000).then(() => fetchFromBackup())
]);
} catch (error) {
return getDefaultData();
}
}
async function mapWithConcurrency(items, fn, concurrency = 3) {
const results = [];
const executing = [];
for (const item of items) {
const promise = Promise.resolve().then(() => fn(item));
results.push(promise);
if (concurrency <= items.length) {
const e = promise.then(() => {
executing.splice(executing.indexOf(e), 1);
});
executing.push(e);
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// Process with max 3 concurrent operations
await mapWithConcurrency(userIds, fetchUser, 3);
async function loadProgressively(items, onProgress) {
const results = [];
let completed = 0;
const promises = items.map(async (item, index) => {
const result = await processItem(item);
completed++;
onProgress(completed, items.length);
return result;
});
return await Promise.all(promises);
}
// Usage
await loadProgressively(items, (completed, total) => {
console.log(`Progress: ${completed}/${total}`);
});
async function processCheckout(cart, user) {
// Step 1: Validate in parallel
const [cartValid, userValid, inventoryValid] = await Promise.all([
validateCart(cart),
validateUser(user),
checkInventory(cart.items)
]);
if (!cartValid || !userValid || !inventoryValid) {
throw new Error('Validation failed');
}
// Step 2: Sequential payment processing
const paymentIntent = await createPaymentIntent(cart.total);
const payment = await processPayment(paymentIntent);
// Step 3: Parallel post-payment operations
const [order, receipt, inventory] = await Promise.all([
createOrder(cart, payment),
generateReceipt(payment),
updateInventory(cart.items)
]);
// Step 4: Send notifications (don't wait)
sendConfirmationEmail(user.email, order).catch(console.error);
return { order, receipt };
}
// Bad: Sequential when parallel is possible
async function bad() {
const a = await fetchA(); // 1s
const b = await fetchB(); // 1s
const c = await fetchC(); // 1s
return [a, b, c]; // 3s total
}
// Good: Parallel execution
async function good() {
return await Promise.all([
fetchA(),
fetchB(),
fetchC()
]); // 1s total
}
// Bad: Overwhelming the server
async function bad(userIds) {
return await Promise.all(
userIds.map(id => fetchUser(id)) // 1000 requests at once!
);
}
// Good: Controlled batching
async function good(userIds) {
return await processBatches(userIds, 10);
}
Now that you've mastered async/await, let's explore advanced Promise patterns!