Skip to content

Asynchronous JavaScript

Asynchronous programming is fundamental to modern JavaScript development. This comprehensive guide covers everything from basic concepts to advanced patterns for handling asynchronous operations.

// Synchronous vs Asynchronous execution
console.log('1: Start');
setTimeout(() => {
console.log('2: Timeout callback');
}, 0);
Promise.resolve().then(() => {
console.log('3: Promise callback');
});
console.log('4: End');
// Output order: 1, 4, 3, 2
// Explanation: Microtasks (Promises) have higher priority than macrotasks (setTimeout)
// Demonstration of different phases
function demonstrateEventLoop() {
console.log('=== Event Loop Demonstration ===');
// 1. Synchronous code (current execution stack)
console.log('1. Synchronous start');
// 2. setTimeout (macrotask queue)
setTimeout(() => console.log('2. setTimeout 0ms'), 0);
setTimeout(() => console.log('3. setTimeout 10ms'), 10);
// 3. setImmediate (Node.js - macrotask queue)
if (typeof setImmediate !== 'undefined') {
setImmediate(() => console.log('4. setImmediate'));
}
// 4. Promise (microtask queue)
Promise.resolve().then(() => console.log('5. Promise.resolve'));
// 5. queueMicrotask (microtask queue)
queueMicrotask(() => console.log('6. queueMicrotask'));
// 6. Nested Promise (microtask queue)
Promise.resolve().then(() => {
console.log('7. Promise nested');
Promise.resolve().then(() => console.log('8. Promise double nested'));
});
console.log('9. Synchronous end');
}
demonstrateEventLoop();
// Understanding microtask vs macrotask priority
function taskPriorityDemo() {
setTimeout(() => console.log('Macrotask 1'), 0);
Promise.resolve().then(() => {
console.log('Microtask 1');
setTimeout(() => console.log('Macrotask 2'), 0);
Promise.resolve().then(() => console.log('Microtask 2'));
});
setTimeout(() => console.log('Macrotask 3'), 0);
}
// Output: Microtask 1, Microtask 2, Macrotask 1, Macrotask 3, Macrotask 2
// Creating Promises
const simplePromise = new Promise((resolve, reject) => {
const success = Math.random() > 0.5;
setTimeout(() => {
if (success) {
resolve('Operation successful!');
} else {
reject(new Error('Operation failed!'));
}
}, 1000);
});
// Promise states and transitions
class PromiseStateTracker {
constructor() {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
}
createTrackedPromise(executor) {
return new Promise((resolve, reject) => {
const trackedResolve = (value) => {
this.state = 'fulfilled';
this.value = value;
resolve(value);
};
const trackedReject = (reason) => {
this.state = 'rejected';
this.reason = reason;
reject(reason);
};
executor(trackedResolve, trackedReject);
});
}
getState() {
return {
state: this.state,
value: this.value,
reason: this.reason
};
}
}
// Using the tracker
const tracker = new PromiseStateTracker();
const trackedPromise = tracker.createTrackedPromise((resolve, reject) => {
setTimeout(() => resolve('Success!'), 1000);
});
console.log(tracker.getState()); // { state: 'pending', value: undefined, reason: undefined }
trackedPromise.then(() => {
console.log(tracker.getState()); // { state: 'fulfilled', value: 'Success!', reason: undefined }
});
// Complex promise chain with error handling
function fetchUserData(userId) {
return fetch(`/api/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(user => {
console.log('User fetched:', user);
return user;
})
.catch(error => {
console.error('Failed to fetch user:', error);
throw error; // Re-throw to allow upstream handling
});
}
function fetchUserPosts(userId) {
return fetch(`/api/users/${userId}/posts`)
.then(response => response.json())
.catch(error => {
console.error('Failed to fetch posts:', error);
return []; // Return empty array as fallback
});
}
// Chaining with error recovery
function getUserWithPosts(userId) {
return fetchUserData(userId)
.then(user => {
return fetchUserPosts(userId)
.then(posts => ({
...user,
posts: posts,
postsCount: posts.length
}));
})
.catch(error => {
// If user fetch fails, return default user object
console.warn('Using fallback user data');
return {
id: userId,
name: 'Unknown User',
posts: [],
postsCount: 0
};
});
}
// Promise.finally usage
function performOperationWithCleanup() {
let loadingSpinner = showLoading();
return performAsyncOperation()
.then(result => {
console.log('Operation successful:', result);
return result;
})
.catch(error => {
console.error('Operation failed:', error);
throw error;
})
.finally(() => {
hideLoading(loadingSpinner);
console.log('Cleanup completed');
});
}
// Promise.all - Wait for all promises to resolve
async function fetchMultipleUsers(userIds) {
try {
const promises = userIds.map(id => fetchUserData(id));
const users = await Promise.all(promises);
console.log('All users fetched:', users);
return users;
} catch (error) {
console.error('One or more user fetches failed:', error);
throw error;
}
}
// Promise.allSettled - Wait for all promises to settle (resolve or reject)
async function fetchUsersWithResults(userIds) {
const promises = userIds.map(id => fetchUserData(id));
const results = await Promise.allSettled(promises);
const successful = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failed = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
return { successful, failed };
}
// Promise.race - Return first promise to settle
async function fetchWithTimeout(url, timeoutMs = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), timeoutMs);
});
return Promise.race([fetchPromise, timeoutPromise]);
}
// Promise.any - Return first promise to resolve
async function fetchFromMultipleSources(urls) {
const promises = urls.map(url => fetch(url));
try {
const firstSuccessful = await Promise.any(promises);
return firstSuccessful;
} catch (aggregateError) {
console.error('All sources failed:', aggregateError.errors);
throw new Error('All fetch attempts failed');
}
}
// Custom Promise utilities
class PromiseUtils {
// Delay utility
static delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Timeout wrapper
static withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Operation timed out')), ms)
);
return Promise.race([promise, timeout]);
}
// Retry with exponential backoff
static async retry(fn, maxAttempts = 3, baseDelay = 1000) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxAttempts) {
throw error;
}
const delay = baseDelay * Math.pow(2, attempt - 1);
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`);
await this.delay(delay);
}
}
}
// Promise pool for controlled concurrency
static async pool(tasks, concurrency = 3) {
const results = [];
const executing = [];
for (const task of tasks) {
const promise = Promise.resolve().then(() => task());
results.push(promise);
if (tasks.length >= concurrency) {
executing.push(promise.then(() => executing.splice(executing.indexOf(promise), 1)));
}
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// Sequential execution
static async sequence(tasks) {
const results = [];
for (const task of tasks) {
const result = await task();
results.push(result);
}
return results;
}
}
// Usage examples
async function demonstratePromiseUtils() {
// Delay
console.log('Starting delay...');
await PromiseUtils.delay(1000);
console.log('Delay completed');
// Timeout
try {
await PromiseUtils.withTimeout(
fetch('/slow-endpoint'),
3000
);
} catch (error) {
console.log('Request timed out');
}
// Retry
const unstableOperation = () => {
if (Math.random() < 0.7) {
throw new Error('Random failure');
}
return 'Success!';
};
try {
const result = await PromiseUtils.retry(unstableOperation, 5, 500);
console.log('Retry succeeded:', result);
} catch (error) {
console.log('All retry attempts failed');
}
// Controlled concurrency
const tasks = Array.from({ length: 10 }, (_, i) =>
() => PromiseUtils.delay(1000).then(() => `Task ${i} completed`)
);
const results = await PromiseUtils.pool(tasks, 3);
console.log('Pool results:', results);
}
// Converting Promises to async/await
function promiseVersion() {
return fetch('/api/user')
.then(response => response.json())
.then(user => {
return fetch(`/api/user/${user.id}/posts`);
})
.then(response => response.json())
.then(posts => {
return { user, posts };
})
.catch(error => {
console.error('Error:', error);
throw error;
});
}
async function asyncAwaitVersion() {
try {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/api/user/${user.id}/posts`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Async function variants
async function asyncFunction() {
return 'async function';
}
const asyncArrow = async () => {
return 'async arrow function';
};
const asyncMethod = {
async method() {
return 'async method';
}
};
class AsyncClass {
async asyncMethod() {
return 'async class method';
}
static async staticAsyncMethod() {
return 'static async method';
}
}
// Comprehensive error handling patterns
class APIClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
if (!response.ok) {
throw new APIError(
`HTTP ${response.status}: ${response.statusText}`,
response.status,
await response.text()
);
}
return await response.json();
} catch (error) {
if (error instanceof APIError) {
throw error;
}
// Network or other errors
throw new APIError(
'Network error or request failed',
0,
error.message
);
}
}
async safeRequest(endpoint, options = {}) {
try {
return await this.request(endpoint, options);
} catch (error) {
console.error(`Request to ${endpoint} failed:`, error);
return null; // or some default value
}
}
}
class APIError extends Error {
constructor(message, status, body) {
super(message);
this.name = 'APIError';
this.status = status;
this.body = body;
}
}
// Multiple async operations with different error handling strategies
async function handleMultipleOperations() {
const client = new APIClient('/api');
try {
// Strategy 1: Fail fast - if any operation fails, stop everything
const user = await client.request('/user');
const settings = await client.request('/settings');
const notifications = await client.request('/notifications');
return { user, settings, notifications };
} catch (error) {
console.error('Failed to load user data:', error);
throw error;
}
}
async function handleMultipleOperationsResilient() {
const client = new APIClient('/api');
// Strategy 2: Continue on errors, collect partial results
const [user, settings, notifications] = await Promise.allSettled([
client.request('/user'),
client.request('/settings'),
client.request('/notifications')
]);
return {
user: user.status === 'fulfilled' ? user.value : null,
settings: settings.status === 'fulfilled' ? settings.value : {},
notifications: notifications.status === 'fulfilled' ? notifications.value : []
};
}
async function handleMultipleOperationsSafe() {
const client = new APIClient('/api');
// Strategy 3: Use safe methods that never throw
const [user, settings, notifications] = await Promise.all([
client.safeRequest('/user'),
client.safeRequest('/settings'),
client.safeRequest('/notifications')
]);
return {
user: user || { name: 'Guest User' },
settings: settings || {},
notifications: notifications || []
};
}
// Async iteration and generators
async function* asyncGenerator() {
for (let i = 0; i < 5; i++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield `Value ${i}`;
}
}
async function consumeAsyncGenerator() {
for await (const value of asyncGenerator()) {
console.log('Received:', value);
}
}
// Async iteration over API results
class APIIterator {
constructor(endpoint, pageSize = 10) {
this.endpoint = endpoint;
this.pageSize = pageSize;
}
async* pages() {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(
`${this.endpoint}?page=${page}&size=${this.pageSize}`
);
const data = await response.json();
yield data.items;
hasMore = data.hasMore;
page++;
}
}
async* items() {
for await (const page of this.pages()) {
for (const item of page) {
yield item;
}
}
}
}
// Usage of async iteration
async function processAllUsers() {
const userIterator = new APIIterator('/api/users', 50);
let count = 0;
for await (const user of userIterator.items()) {
await processUser(user);
count++;
if (count % 100 === 0) {
console.log(`Processed ${count} users`);
}
}
console.log(`Total users processed: ${count}`);
}
// Parallel execution patterns
async function parallelProcessing() {
const urls = [
'/api/data1',
'/api/data2',
'/api/data3',
'/api/data4',
'/api/data5'
];
// Method 1: Promise.all (all at once)
const allAtOnce = await Promise.all(
urls.map(url => fetch(url).then(r => r.json()))
);
// Method 2: Controlled concurrency
async function fetchWithConcurrency(urls, concurrency = 2) {
const results = [];
const executing = [];
for (const url of urls) {
const promise = fetch(url).then(r => r.json());
results.push(promise);
if (urls.length >= concurrency) {
executing.push(promise.then(() => executing.splice(executing.indexOf(promise), 1)));
}
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
const controlled = await fetchWithConcurrency(urls, 2);
// Method 3: Sequential with progress
async function fetchSequentially(urls) {
const results = [];
for (let i = 0; i < urls.length; i++) {
console.log(`Fetching ${i + 1}/${urls.length}: ${urls[i]}`);
const response = await fetch(urls[i]);
const data = await response.json();
results.push(data);
}
return results;
}
const sequential = await fetchSequentially(urls);
return { allAtOnce, controlled, sequential };
}
// Async queue implementation
class AsyncQueue {
constructor(concurrency = 1) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
async add(task) {
return new Promise((resolve, reject) => {
this.queue.push({
task,
resolve,
reject
});
this.process();
});
}
async process() {
if (this.running >= this.concurrency || this.queue.length === 0) {
return;
}
this.running++;
const { task, resolve, reject } = this.queue.shift();
try {
const result = await task();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process();
}
}
async clear() {
this.queue = [];
}
get size() {
return this.queue.length;
}
get pending() {
return this.running;
}
}
// Usage of async queue
async function demonstrateAsyncQueue() {
const queue = new AsyncQueue(2); // Max 2 concurrent operations
const tasks = Array.from({ length: 10 }, (_, i) =>
() => new Promise(resolve =>
setTimeout(() => resolve(`Task ${i} completed`), Math.random() * 1000)
)
);
const results = await Promise.all(
tasks.map(task => queue.add(task))
);
console.log('All tasks completed:', results);
}
// Using AbortController for cancellation
class CancellableOperation {
constructor() {
this.controller = new AbortController();
this.signal = this.controller.signal;
}
async fetchWithCancellation(url) {
try {
const response = await fetch(url, { signal: this.signal });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Operation was cancelled');
throw new Error('Operation cancelled');
}
throw error;
}
}
cancel() {
this.controller.abort();
}
isCancelled() {
return this.signal.aborted;
}
}
// Custom cancellation token
class CancellationToken {
constructor() {
this.cancelled = false;
this.callbacks = [];
}
cancel() {
if (this.cancelled) return;
this.cancelled = true;
this.callbacks.forEach(callback => callback());
this.callbacks = [];
}
throwIfCancelled() {
if (this.cancelled) {
throw new Error('Operation was cancelled');
}
}
onCancelled(callback) {
if (this.cancelled) {
callback();
} else {
this.callbacks.push(callback);
}
}
}
async function cancellableAsyncOperation(token, data) {
for (let i = 0; i < data.length; i++) {
token.throwIfCancelled();
// Simulate some work
await new Promise(resolve => setTimeout(resolve, 100));
// Process item
console.log(`Processing item ${i}`);
}
return 'Operation completed';
}
// Timeout with cancellation
async function withTimeout(promise, ms) {
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, ms);
try {
const result = await promise;
clearTimeout(timeout);
return result;
} catch (error) {
clearTimeout(timeout);
if (controller.signal.aborted) {
throw new Error('Operation timed out');
}
throw error;
}
}
// Observable-like pattern with async iterators
class AsyncSubject {
constructor() {
this.observers = [];
this.value = undefined;
this.hasValue = false;
}
next(value) {
this.value = value;
this.hasValue = true;
this.observers.forEach(observer => {
if (!observer.closed) {
observer.queue.push(value);
observer.resolve();
}
});
}
complete() {
this.observers.forEach(observer => {
observer.closed = true;
observer.resolve();
});
}
error(error) {
this.observers.forEach(observer => {
observer.closed = true;
observer.reject(error);
});
}
async* subscribe() {
const observer = {
queue: [],
closed: false,
resolve: null,
reject: null,
waiting: false
};
this.observers.push(observer);
// If we already have a value, emit it immediately
if (this.hasValue) {
observer.queue.push(this.value);
}
try {
while (!observer.closed) {
if (observer.queue.length > 0) {
yield observer.queue.shift();
} else {
observer.waiting = true;
await new Promise((resolve, reject) => {
observer.resolve = resolve;
observer.reject = reject;
});
observer.waiting = false;
}
}
} finally {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
}
}
// Usage of reactive pattern
async function demonstrateReactivePattern() {
const subject = new AsyncSubject();
// Consumer
const subscription = subject.subscribe();
(async () => {
for await (const value of subscription) {
console.log('Received:', value);
}
console.log('Subscription ended');
})();
// Producer
await new Promise(resolve => setTimeout(resolve, 1000));
subject.next('First value');
await new Promise(resolve => setTimeout(resolve, 1000));
subject.next('Second value');
await new Promise(resolve => setTimeout(resolve, 1000));
subject.complete();
}
// Async state machine
class AsyncStateMachine {
constructor(initialState) {
this.state = initialState;
this.transitions = new Map();
this.middleware = [];
}
addTransition(from, to, action) {
if (!this.transitions.has(from)) {
this.transitions.set(from, new Map());
}
this.transitions.get(from).set(to, action);
}
addMiddleware(middleware) {
this.middleware.push(middleware);
}
async transition(to, payload) {
const from = this.state;
if (!this.transitions.has(from) || !this.transitions.get(from).has(to)) {
throw new Error(`Invalid transition from ${from} to ${to}`);
}
// Execute middleware
for (const middleware of this.middleware) {
await middleware(from, to, payload);
}
const action = this.transitions.get(from).get(to);
try {
await action(payload);
this.state = to;
console.log(`State changed: ${from} -> ${to}`);
} catch (error) {
console.error(`Transition failed: ${from} -> ${to}`, error);
throw error;
}
}
getCurrentState() {
return this.state;
}
}
// Example: Order processing state machine
async function createOrderStateMachine() {
const machine = new AsyncStateMachine('created');
// Add logging middleware
machine.addMiddleware(async (from, to, payload) => {
console.log(`Transitioning from ${from} to ${to}`, payload);
});
// Define transitions
machine.addTransition('created', 'processing', async (order) => {
console.log('Processing order:', order.id);
await simulateAsyncWork(1000);
});
machine.addTransition('processing', 'shipped', async (order) => {
console.log('Shipping order:', order.id);
await simulateAsyncWork(2000);
});
machine.addTransition('shipped', 'delivered', async (order) => {
console.log('Delivering order:', order.id);
await simulateAsyncWork(3000);
});
machine.addTransition('processing', 'cancelled', async (order) => {
console.log('Cancelling order:', order.id);
await simulateAsyncWork(500);
});
return machine;
}
function simulateAsyncWork(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

This comprehensive guide to asynchronous JavaScript covers everything from fundamental concepts to advanced patterns. Understanding these concepts is essential for building efficient, responsive JavaScript applications.


Master these async patterns to handle complex asynchronous operations with confidence and efficiency.