Skip to main content

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')),
]);