Asynchronous JavaScript
Asynchronous JavaScript
Section titled “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.
Understanding the Event Loop
Section titled “Understanding the Event Loop”The JavaScript Runtime Model
Section titled “The JavaScript Runtime Model”// Synchronous vs Asynchronous executionconsole.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)
Event Loop Phases
Section titled “Event Loop Phases”// Demonstration of different phasesfunction 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 priorityfunction 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
Promises Deep Dive
Section titled “Promises Deep Dive”Promise Fundamentals
Section titled “Promise Fundamentals”// Creating Promisesconst 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 transitionsclass 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 trackerconst 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 }});
Promise Chaining and Error Handling
Section titled “Promise Chaining and Error Handling”// Complex promise chain with error handlingfunction 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 recoveryfunction 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 usagefunction 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 Static Methods
Section titled “Promise Static Methods”// Promise.all - Wait for all promises to resolveasync 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 settleasync 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 resolveasync 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 utilitiesclass 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 examplesasync 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);}
Async/Await
Section titled “Async/Await”Async/Await Fundamentals
Section titled “Async/Await Fundamentals”// Converting Promises to async/awaitfunction 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 variantsasync 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'; }}
Error Handling with Async/Await
Section titled “Error Handling with Async/Await”// Comprehensive error handling patternsclass 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 strategiesasync 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 || [] };}
Advanced Async Patterns
Section titled “Advanced Async Patterns”// Async iteration and generatorsasync 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 resultsclass 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 iterationasync 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 patternsasync 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 implementationclass 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 queueasync 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);}
Advanced Async Patterns
Section titled “Advanced Async Patterns”Cancellation and AbortController
Section titled “Cancellation and AbortController”// Using AbortController for cancellationclass 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 tokenclass 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 cancellationasync 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; }}
Reactive Async Patterns
Section titled “Reactive Async Patterns”// Observable-like pattern with async iteratorsclass 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 patternasync 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 machineclass 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 machineasync 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.