Promises
Promises provide a cleaner way to handle asynchronous operations in JavaScript, avoiding callback hell and providing better error handling.
Understanding Promises
Promise States
javascript// A Promise has three states: // 1. Pending - initial state, neither fulfilled nor rejected // 2. Fulfilled - operation completed successfully // 3. Rejected - operation failed // Creating a basic Promise const basicPromise = new Promise((resolve, reject) => { const success = true; if (success) { resolve('Operation successful!'); } else { reject('Operation failed!'); } }); console.log(basicPromise); // Promise {<fulfilled>: 'Operation successful!'}
Creating Promises
javascript// Simple resolved Promise const resolvedPromise = Promise.resolve('Immediate success'); const rejectedPromise = Promise.reject('Immediate failure'); // Promise with setTimeout function delay(ms) { return new Promise(resolve => { setTimeout(() => resolve(`Waited ${ms}ms`), ms); }); } // Promise with random success/failure function randomPromise() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { resolve('Success!'); } else { reject('Failed!'); } }, 1000); }); } // Simulating API call function fetchUser(id) { return new Promise((resolve, reject) => { setTimeout(() => { if (id > 0) { resolve({ id: id, name: `User ${id}`, email: `user${id}@example.com` }); } else { reject('Invalid user ID'); } }, 500); }); }
Promise Methods
.then() and .catch()
javascript// Basic then/catch usage fetchUser(1) .then(user => { console.log('User found:', user); return user.name; // Return value for next then }) .then(name => { console.log('User name:', name); }) .catch(error => { console.error('Error:', error); }); // Multiple then chains delay(1000) .then(result => { console.log(result); // 'Waited 1000ms' return delay(500); }) .then(result => { console.log(result); // 'Waited 500ms' return 'Final result'; }) .then(result => { console.log(result); // 'Final result' }); // Error handling in chains fetchUser(-1) .then(user => { console.log('This won\'t run'); return user.name; }) .catch(error => { console.error('Caught error:', error); return 'Default User'; // Recovery value }) .then(name => { console.log('Name (or default):', name); // 'Default User' });
.finally()
javascriptfunction performOperation() { console.log('Starting operation...'); return randomPromise() .then(result => { console.log('Success:', result); return result; }) .catch(error => { console.error('Error:', error); throw error; // Re-throw to maintain rejection }) .finally(() => { console.log('Operation completed (cleanup)'); }); } performOperation();
Promise Static Methods
Promise.all()
javascript// Wait for all promises to resolve const promise1 = delay(1000).then(() => 'First'); const promise2 = delay(2000).then(() => 'Second'); const promise3 = delay(1500).then(() => 'Third'); Promise.all([promise1, promise2, promise3]) .then(results => { console.log('All completed:', results); // ['First', 'Second', 'Third'] - after 2 seconds }) .catch(error => { console.error('One failed:', error); }); // Practical example: Multiple API calls async function fetchMultipleUsers() { const userPromises = [ fetchUser(1), fetchUser(2), fetchUser(3) ]; try { const users = await Promise.all(userPromises); console.log('All users:', users); } catch (error) { console.error('Failed to fetch users:', error); } } // If any promise rejects, Promise.all rejects immediately const mixedPromises = [ Promise.resolve('Success 1'), Promise.reject('Error!'), Promise.resolve('Success 2') ]; Promise.all(mixedPromises) .then(results => { console.log('Won\'t reach here'); }) .catch(error => { console.error('Failed fast:', error); // 'Error!' });
Promise.allSettled()
javascript// Wait for all promises to settle (resolve or reject) Promise.allSettled([ Promise.resolve('Success'), Promise.reject('Error'), delay(1000).then(() => 'Delayed success') ]) .then(results => { console.log('All settled:', results); /* [ {status: 'fulfilled', value: 'Success'}, {status: 'rejected', reason: 'Error'}, {status: 'fulfilled', value: 'Delayed success'} ] */ // Process 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: Batch operations with error tolerance async function batchProcess(items) { const promises = items.map(item => processItem(item)); 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 }; } function processItem(item) { return new Promise((resolve, reject) => { setTimeout(() => { if (item > 0) { resolve(`Processed ${item}`); } else { reject(`Invalid item: ${item}`); } }, Math.random() * 1000); }); } // batchProcess([1, -1, 2, -2, 3]).then(console.log);
Promise.race()
javascript// Resolves/rejects with the first settled promise const fast = delay(100).then(() => 'Fast'); const slow = delay(1000).then(() => 'Slow'); Promise.race([fast, slow]) .then(result => { console.log('Winner:', result); // 'Fast' }); // Timeout implementation function withTimeout(promise, ms) { const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error('Timeout')), ms); }); return Promise.race([promise, timeout]); } // Usage withTimeout(delay(2000), 1000) .then(result => { console.log('Completed in time:', result); }) .catch(error => { console.error('Timed out:', error.message); }); // First successful response function fetchFromMultipleSources(urls) { const promises = urls.map(url => fetch(url)); return Promise.race(promises); }
Promise.any()
javascript// Resolves with the first fulfilled promise const promises = [ Promise.reject('Error 1'), delay(1000).then(() => 'Success 1'), delay(500).then(() => 'Success 2'), Promise.reject('Error 2') ]; Promise.any(promises) .then(result => { console.log('First success:', result); // 'Success 2' }) .catch(error => { console.error('All failed:', error); }); // If all promises reject, Promise.any rejects with AggregateError const allRejects = [ Promise.reject('Error 1'), Promise.reject('Error 2'), Promise.reject('Error 3') ]; Promise.any(allRejects) .then(result => { console.log('Won\'t reach here'); }) .catch(error => { console.error('All failed:', error.errors); // ['Error 1', 'Error 2', 'Error 3'] });
Practical Examples
API Request with Retry Logic
javascriptfunction fetchWithRetry(url, maxRetries = 3) { return new Promise((resolve, reject) => { let attempts = 0; function attempt() { attempts++; fetch(url) .then(response => { if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.json(); }) .then(resolve) .catch(error => { if (attempts < maxRetries) { console.log(`Attempt ${attempts} failed, retrying...`); setTimeout(attempt, 1000 * attempts); // Exponential backoff } else { reject(error); } }); } attempt(); }); } // Usage fetchWithRetry('https://api.example.com/data') .then(data => console.log('Data:', data)) .catch(error => console.error('Failed after retries:', error));
Sequential Promise Execution
javascript// Execute promises one after another function executeSequentially(promises) { return promises.reduce((chain, currentPromise) => { return chain.then(results => { return currentPromise().then(result => { return [...results, result]; }); }); }, Promise.resolve([])); } // Example usage const tasks = [ () => delay(1000).then(() => 'Task 1'), () => delay(500).then(() => 'Task 2'), () => delay(800).then(() => 'Task 3') ]; executeSequentially(tasks) .then(results => { console.log('Sequential results:', results); // ['Task 1', 'Task 2', 'Task 3'] - after 2.3 seconds total }); // Alternative using async/await async function executeSequentiallyAsync(tasks) { const results = []; for (const task of tasks) { const result = await task(); results.push(result); } return results; }
Promise-based Event Emitter
javascriptclass PromiseEventEmitter { constructor() { this.events = new Map(); } on(event, listener) { if (!this.events.has(event)) { this.events.set(event, []); } this.events.get(event).push(listener); } emit(event, data) { if (this.events.has(event)) { const promises = this.events.get(event).map(listener => { return Promise.resolve(listener(data)); }); return Promise.all(promises); } return Promise.resolve([]); } once(event) { return new Promise(resolve => { const listener = (data) => { resolve(data); // Remove listener after first call const listeners = this.events.get(event); const index = listeners.indexOf(listener); if (index > -1) { listeners.splice(index, 1); } }; this.on(event, listener); }); } } // Usage const emitter = new PromiseEventEmitter(); emitter.on('data', async (data) => { console.log('Processing:', data); await delay(100); return `Processed: ${data}`; }); // Wait for next 'data' event emitter.once('data').then(data => { console.log('Received:', data); }); // Emit event emitter.emit('data', 'Hello World');
Promisifying Callback Functions
javascript// Convert callback-based function to Promise function promisify(fn) { return function(...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); }; } // Example: Promisify setTimeout const setTimeoutPromise = promisify(setTimeout); // Usage (though delay() function above is better) setTimeoutPromise(1000).then(() => { console.log('Timer completed'); }); // Promisify Node.js fs.readFile (example) // const fs = require('fs'); // const readFilePromise = promisify(fs.readFile); // // readFilePromise('file.txt', 'utf8') // .then(content => console.log(content)) // .catch(error => console.error(error));
Promise Pool (Concurrency Control)
javascriptclass PromisePool { constructor(concurrency = 3) { this.concurrency = concurrency; this.running = 0; this.queue = []; } add(promiseFunction) { return new Promise((resolve, reject) => { this.queue.push({ promiseFunction, resolve, reject }); this.process(); }); } async process() { if (this.running >= this.concurrency || this.queue.length === 0) { return; } this.running++; const { promiseFunction, resolve, reject } = this.queue.shift(); try { const result = await promiseFunction(); resolve(result); } catch (error) { reject(error); } finally { this.running--; this.process(); // Process next item } } } // Usage const pool = new PromisePool(2); // Max 2 concurrent operations const tasks = Array.from({length: 10}, (_, i) => () => delay(1000).then(() => `Task ${i + 1}`) ); // Add all tasks to pool const promises = tasks.map(task => pool.add(task)); Promise.all(promises).then(results => { console.log('All tasks completed:', results); });