Async Programming
Promises
Promise Creation
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
// Promise methods
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Done'));
Promise States
// Pending → Fulfilled
const fulfilledPromise = Promise.resolve('Success');
// Pending → Rejected
const rejectedPromise = Promise.reject(new Error('Failed'));
// Promise that settles after delay
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
// Promise with timeout
const withTimeout = (promise, ms) => {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
return Promise.race([promise, timeout]);
};
Async/Await
Basic Async/Await
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Usage
fetchData().then(data => console.log(data));
// Or in another async function
async function main() {
const data = await fetchData();
console.log(data);
}
Async Function Patterns
// Async arrow function
const fetchUser = async id => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
// Async method in object
const api = {
async getUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
};
// Async method in class
class UserService {
async fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
}
Promise Utilities
Promise.all
// Wait for all promises to resolve
const promises = [
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments'),
];
Promise.all(promises)
.then(responses => {
// All resolved - process responses
return Promise.all(responses.map(r => r.json()));
})
.then(data => {
const [users, posts, comments] = data;
console.log({ users, posts, comments });
})
.catch(error => {
// If any promise rejects, this catch runs
console.error('One or more requests failed:', error);
});
// With async/await
async function fetchAllData() {
try {
const [usersRes, postsRes, commentsRes] = await Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments'),
]);
const [users, posts, comments] = await Promise.all([
usersRes.json(),
postsRes.json(),
commentsRes.json(),
]);
return { users, posts, comments };
} catch (error) {
console.error('Failed to fetch data:', error);
throw error;
}
}
Promise.allSettled
// Wait for all promises to settle (resolve or reject)
const promises = [
fetch('/api/reliable'),
fetch('/api/unreliable'),
fetch('/api/sometimes-fails'),
];
Promise.allSettled(promises).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} succeeded:`, result.value);
} else {
console.log(`Promise ${index} failed:`, result.reason);
}
});
});
// Practical example: multiple API calls where some might fail
async function fetchWithFallbacks() {
const results = await Promise.allSettled([
fetch('/api/primary'),
fetch('/api/secondary'),
fetch('/api/tertiary'),
]);
const successful = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
if (successful.length === 0) {
throw new Error('All API calls failed');
}
return successful[0]; // Use first successful response
}
Promise.race
// First promise to settle (resolve or reject) wins
const promises = [
fetch('/api/server1'),
fetch('/api/server2'),
fetch('/api/server3'),
];
Promise.race(promises)
.then(response => {
console.log('First server responded:', response);
})
.catch(error => {
console.log('First server to respond failed:', error);
});
// Timeout implementation
function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), timeout);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
Promise.any (ES2021)
// First promise to fulfill (reject only if all reject)
const promises = [
fetch('/api/server1'),
fetch('/api/server2'),
fetch('/api/server3'),
];
Promise.any(promises)
.then(response => {
console.log('First successful response:', response);
})
.catch(error => {
// AggregateError - all promises rejected
console.log('All requests failed:', error.errors);
});
Advanced Async Patterns
Sequential vs Parallel Execution
// Sequential execution (slower)
async function sequential() {
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();
return { user, posts, comments };
}
// Parallel execution (faster)
async function parallel() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments(),
]);
return { user, posts, comments };
}
// Mixed: some sequential, some parallel
async function mixed() {
const user = await fetchUser(); // Must complete first
// These can run in parallel
const [posts, friends] = await Promise.all([
fetchUserPosts(user.id),
fetchUserFriends(user.id),
]);
return { user, posts, friends };
}
Async Iteration
// 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 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 batchPromises = batch.map(item => processItem(item));
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
Async Generators
// Async generator for streaming data
async function* fetchPages(url) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}`);
const data = await response.json();
yield data.items;
hasMore = data.hasNext;
page++;
}
}
// Using async generator
async function processAllPages() {
for await (const pageItems of fetchPages('/api/items')) {
console.log('Processing page items:', pageItems);
}
}
Retry Logic
async function retry(fn, maxAttempts = 3, delay = 1000) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) {
throw error;
}
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
// Optional: exponential backoff
delay *= 2;
}
}
}
// Usage
const data = await retry(
() => fetch('/api/unreliable').then(r => r.json()),
3,
1000
);
Queue/Pool Pattern
class AsyncQueue {
constructor(concurrency = 1) {
this.concurrency = concurrency;
this.running = [];
this.queue = [];
}
async add(fn) {
return new Promise((resolve, reject) => {
this.queue.push({
fn,
resolve,
reject,
});
this.process();
});
}
async process() {
if (this.running.length >= this.concurrency || this.queue.length === 0) {
return;
}
const { fn, resolve, reject } = this.queue.shift();
const promise = fn()
.then(resolve)
.catch(reject)
.finally(() => {
this.running.splice(this.running.indexOf(promise), 1);
this.process();
});
this.running.push(promise);
}
}
// Usage
const queue = new AsyncQueue(3); // Max 3 concurrent operations
const results = await Promise.all([
queue.add(() => fetch('/api/1')),
queue.add(() => fetch('/api/2')),
queue.add(() => fetch('/api/3')),
queue.add(() => fetch('/api/4')),
queue.add(() => fetch('/api/5')),
]);