Master asynchronous JavaScript with practical, real-world examples. Learn how to handle API calls, errors, and parallel execution with clean, readable code.
Async/await is syntactic sugar built on top of Promises, making asynchronous code look and behave more like synchronous code. It was introduced in ES2017 (ES8) and has become the preferred way to handle asynchronous operations in modern JavaScript.
The simplest example of an async function. Any function marked with async automatically returns a Promise.
// Basic async function
async function greet() {
return 'Hello, World!';
}
// Calling async function
greet().then(message => console.log(message));
// Output: Hello, World!
// Using await (must be inside async function)
async function main() {
const message = await greet();
console.log(message); // Hello, World!
}
The most common use case: fetching data from a REST API using the Fetch API with async/await.
async function fetchUser(userId) {
// Fetch data from API
const response = await fetch(`https://api.example.com/users/${userId}`);
// Parse JSON response
const user = await response.json();
return user;
}
// Usage
async function displayUser() {
const user = await fetchUser(1);
console.log(user.name);
}
Proper error handling is crucial. Use try/catch blocks to gracefully handle errors in async functions.
async function fetchUserSafely(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
// Check if response is OK
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('Failed to fetch user:', error.message);
return null; // Return fallback value
}
}
// Usage
const user = await fetchUserSafely(1);
if (user) {
console.log(user.name);
} else {
console.log('User not found');
}
response.ok before parsing JSONUnderstanding when to run async operations sequentially (one after another) vs in parallel (simultaneously) can dramatically improve performance.
// ❌ SLOW: Sequential (one after another)
async function fetchSequential() {
const user = await fetch('/api/user').then(r => r.json());
const posts = await fetch('/api/posts').then(r => r.json());
const comments = await fetch('/api/comments').then(r => r.json());
return { user, posts, comments };
// Total time: ~3 seconds (1s + 1s + 1s)
}
// ✅ FAST: Parallel (all at once)
async function fetchParallel() {
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { user, posts, comments };
// Total time: ~1 second (all requests at once!)
}
Promise.all() for parallel execution - it's much faster!Promise.allSettled() if you want all requests to complete even if some failSending data to a server using POST requests with async/await.
async function createUser(userData) {
try {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const newUser = await response.json();
return newUser;
} catch (error) {
console.error('Failed to create user:', error);
throw error; // Re-throw for caller to handle
}
}
// Usage
const newUser = await createUser({
name: 'John Doe',
email: 'john@example.com',
age: 30
});
Automatically retry failed requests with exponential backoff - a common pattern for handling intermittent failures.
// Helper function to create delay
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i <= maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
// If this was the last retry, throw the error
if (i === maxRetries) {
throw new Error(`Failed after ${maxRetries} retries: ${error.message}`);
}
// Calculate delay with exponential backoff
const delayTime = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Retry ${i + 1}/${maxRetries} after ${delayTime}ms...`);
await delay(delayTime);
}
}
}
// Usage
const data = await fetchWithRetry('https://api.example.com/data', 3);
maxRetries based on your use caseProcessing arrays with async operations efficiently - mapping, filtering, and reducing with async/await.
// Process array items sequentially
async function processSequentially(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// Process array items in parallel
async function processInParallel(items) {
const promises = items.map(item => processItem(item));
return await Promise.all(promises);
}
// Process with concurrency limit
async function processWithLimit(items, limit = 3) {
const results = [];
for (let i = 0; i < items.length; i += limit) {
const batch = items.slice(i, i + limit);
const batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
}
return results;
}
// Example: Fetch user details for multiple IDs
const userIds = [1, 2, 3, 4, 5];
const users = await processInParallel(
userIds.map(id => fetch(`/api/users/${id}`).then(r => r.json()))
);
Cancel async operations that take too long using Promise.race() with a timeout.
// Create a timeout promise
function timeout(ms, message = 'Operation timed out') {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error(message)), ms);
});
}
// Fetch with timeout
async function fetchWithTimeout(url, timeoutMs = 5000) {
try {
const response = await Promise.race([
fetch(url),
timeout(timeoutMs, `Request timed out after ${timeoutMs}ms`)
]);
return await response.json();
} catch (error) {
if (error.message.includes('timed out')) {
console.error('Request was too slow');
}
throw error;
}
}
// Usage with 3 second timeout
const data = await fetchWithTimeout('https://api.example.com/slow-endpoint', 3000);