Master Promise constructor, chaining, error handling, and comparison with callbacks for async programming.
Promises revolutionized asynchronous JavaScript programming by providing a cleaner, more manageable alternative to callback-based approaches. Before Promises, handling asynchronous operations often led to "callback hell" - deeply nested, difficult-to-maintain code. Promises introduce a chainable, predictable way to handle asynchronous operations, making your code more readable and easier to debug.
A Promise represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Unlike callbacks, Promises provide built-in error handling, allow for easy composition of multiple async operations, and support powerful patterns like Promise.all() and Promise.race() for handling multiple concurrent operations.
Understanding Promises is essential for modern JavaScript development, as they form the foundation for async/await syntax, modern APIs, and framework patterns. They're used extensively in web APIs, Node.js, and any scenario involving network requests, file operations, or time-based operations.
Promises are objects that represent the eventual completion or failure of an asynchronous operation. They have three states: pending, fulfilled, or rejected, and provide a clean way to handle asynchronous operations.
Creating Promises:
The Promise constructor takes a function (executor) with resolve and reject parameters.
1// Basic Promise creation
2const basicPromise = new Promise((resolve, reject) => {
3 // Simulate async operation
4 setTimeout(() => {
5 const success = Math.random() > 0.5;
6 if (success) {
7 resolve("Operation completed successfully!");
8 } else {
9 reject(new Error("Operation failed!"));
10 }
11 }, 1000);
12});
13
14// Using the Promise
15basicPromise
16 .then(result => {
17 console.log("Success:", result);
18 })
19 .catch(error => {
20 console.error("Error:", error.message);
21 })
22 .finally(() => {
23 console.log("Promise completed");
24 });Promise States and Lifecycle:
1// Helper function to demonstrate Promise states
2function createDelayedPromise(delay, shouldResolve = true) {
3 return new Promise((resolve, reject) => {
4 console.log("Promise created - State: pending");
5
6 setTimeout(() => {
7 if (shouldResolve) {
8 resolve(`Resolved after ${delay}ms`);
9 // State is now: fulfilled
10 } else {
11 reject(new Error(`Rejected after ${delay}ms`));
12 // State is now: rejected
13 }
14 }, delay);
15 });
16}
17
18// Demonstrate state transitions
19const promise1 = createDelayedPromise(1000, true);
20const promise2 = createDelayedPromise(1500, false);
21
22// Promises are immutable - once settled, they can't change state
23promise1.then(value => {
24 console.log("Promise 1 fulfilled:", value);
25});
26
27promise2.catch(error => {
28 console.log("Promise 2 rejected:", error.message);
29});Promise Methods - then, catch, finally:
1// Comprehensive Promise handling
2function fetchUserData(userId) {
3 return new Promise((resolve, reject) => {
4 // Simulate API call
5 setTimeout(() => {
6 if (userId > 0) {
7 resolve({
8 id: userId,
9 name: `User ${userId}`,
10 email: `user${userId}@example.com`,
11 createdAt: new Date()
12 });
13 } else {
14 reject(new Error("Invalid user ID"));
15 }
16 }, 800);
17 });
18}
19
20// Using all Promise methods
21fetchUserData(123)
22 .then(user => {
23 console.log("User data received:", user);
24 // Transform the data
25 return {
26 ...user,
27 displayName: `${user.name} (${user.email})`
28 };
29 })
30 .then(transformedUser => {
31 console.log("Transformed user:", transformedUser);
32 // Return another promise for chaining
33 return fetchUserData(456); // Chain another async operation
34 })
35 .then(secondUser => {
36 console.log("Second user:", secondUser);
37 })
38 .catch(error => {
39 console.error("Error in Promise chain:", error.message);
40 // Error handling for any step in the chain
41 })
42 .finally(() => {
43 console.log("User fetching operations completed");
44 // Always executes, regardless of success or failure
45 });Promise Constructor Patterns:
1// Promisifying callback-based functions
2function promisify(callbackFunction) {
3 return function(...args) {
4 return new Promise((resolve, reject) => {
5 callbackFunction(...args, (error, result) => {
6 if (error) {
7 reject(error);
8 } else {
9 resolve(result);
10 }
11 });
12 });
13 };
14}
15
16// Example: Promisifying setTimeout
17function delay(ms) {
18 return new Promise(resolve => {
19 setTimeout(resolve, ms);
20 });
21}
22
23// Usage
24delay(2000).then(() => {
25 console.log("2 seconds have passed");
26});
27
28// Promisifying file operations (Node.js example)
29const fs = require('fs');
30const readFilePromise = promisify(fs.readFile);
31
32readFilePromise('config.json', 'utf8')
33 .then(data => JSON.parse(data))
34 .then(config => console.log("Config loaded:", config))
35 .catch(error => console.error("Failed to load config:", error));Promises provide a solid foundation for handling asynchronous operations with clear state management and error handling.
Promise chaining allows you to perform sequential asynchronous operations in a clean, readable manner. Each .then() returns a new Promise, enabling powerful composition patterns.
Basic Promise Chaining:
1// Sequential API calls example
2function fetchUser(userId) {
3 return new Promise(resolve => {
4 setTimeout(() => {
5 resolve({ id: userId, name: `User ${userId}`, role: 'user' });
6 }, 500);
7 });
8}
9
10function fetchUserPosts(userId) {
11 return new Promise(resolve => {
12 setTimeout(() => {
13 resolve([
14 { id: 1, title: 'First Post', userId },
15 { id: 2, title: 'Second Post', userId }
16 ]);
17 }, 300);
18 });
19}
20
21function fetchPostComments(postId) {
22 return new Promise(resolve => {
23 setTimeout(() => {
24 resolve([
25 { id: 1, text: 'Great post!', postId },
26 { id: 2, text: 'Thanks for sharing', postId }
27 ]);
28 }, 200);
29 });
30}
31
32// Chain multiple async operations
33fetchUser(123)
34 .then(user => {
35 console.log("1. Fetched user:", user);
36 return fetchUserPosts(user.id); // Return Promise for chaining
37 })
38 .then(posts => {
39 console.log("2. Fetched posts:", posts);
40 // Return Promise for first post's comments
41 return fetchPostComments(posts[0].id);
42 })
43 .then(comments => {
44 console.log("3. Fetched comments:", comments);
45 return comments.length; // Return value (automatically wrapped in Promise)
46 })
47 .then(commentCount => {
48 console.log("4. Total comments:", commentCount);
49 })
50 .catch(error => {
51 console.error("Error in chain:", error);
52 });Data Transformation in Chains:
1// Processing pipeline with Promises
2function processUserData(rawData) {
3 return Promise.resolve(rawData) // Start with resolved Promise
4 .then(data => {
5 // Step 1: Validate data
6 if (!data.email || !data.name) {
7 throw new Error("Invalid user data: missing required fields");
8 }
9 return data;
10 })
11 .then(data => {
12 // Step 2: Normalize data
13 return {
14 ...data,
15 email: data.email.toLowerCase(),
16 name: data.name.trim(),
17 createdAt: new Date()
18 };
19 })
20 .then(data => {
21 // Step 3: Add computed fields
22 return {
23 ...data,
24 displayName: `${data.name} <${data.email}>`,
25 username: data.email.split('@')[0]
26 };
27 })
28 .then(data => {
29 // Step 4: Save to database (simulated)
30 return new Promise(resolve => {
31 setTimeout(() => {
32 resolve({ ...data, id: Math.floor(Math.random() * 1000) });
33 }, 100);
34 });
35 });
36}
37
38// Usage
39const rawUserData = {
40 name: " John Doe ",
41 email: "[email protected]",
42 age: 30
43};
44
45processUserData(rawUserData)
46 .then(processedUser => {
47 console.log("Processed user:", processedUser);
48 })
49 .catch(error => {
50 console.error("Processing failed:", error.message);
51 });Error Handling in Chains:
1// Sophisticated error handling and recovery
2function robustDataProcessing(data) {
3 return Promise.resolve(data)
4 .then(data => {
5 // Validation step
6 if (!data) throw new Error("No data provided");
7 return data;
8 })
9 .then(data => {
10 // Risky operation that might fail
11 if (Math.random() < 0.3) {
12 throw new Error("Random processing failure");
13 }
14 return { ...data, processed: true };
15 })
16 .catch(error => {
17 console.warn("Processing failed, attempting recovery:", error.message);
18 // Error recovery - return default data
19 return {
20 error: true,
21 errorMessage: error.message,
22 fallbackData: "Default safe data"
23 };
24 })
25 .then(result => {
26 // This runs whether recovery happened or not
27 console.log("Final processing step:", result);
28 return result;
29 });
30}
31
32// Test error handling
33robustDataProcessing({ original: "test data" })
34 .then(result => {
35 console.log("Final result:", result);
36 })
37 .catch(error => {
38 // This should rarely execute due to recovery logic
39 console.error("Unrecoverable error:", error);
40 });Conditional Chaining:
1// Conditional operations in Promise chains
2function conditionalProcessing(user) {
3 return Promise.resolve(user)
4 .then(user => {
5 console.log("Processing user:", user.name);
6
7 // Conditional branching in Promise chain
8 if (user.role === 'admin') {
9 return fetchAdminData(user.id);
10 } else if (user.role === 'premium') {
11 return fetchPremiumFeatures(user.id);
12 } else {
13 return Promise.resolve(user); // Continue with existing data
14 }
15 })
16 .then(enhancedUser => {
17 // Apply common processing to all user types
18 return {
19 ...enhancedUser,
20 lastProcessed: new Date(),
21 isProcessed: true
22 };
23 });
24}
25
26function fetchAdminData(userId) {
27 return new Promise(resolve => {
28 setTimeout(() => {
29 resolve({
30 id: userId,
31 adminLevel: 5,
32 permissions: ['read', 'write', 'delete', 'manage'],
33 dashboardAccess: true
34 });
35 }, 300);
36 });
37}
38
39function fetchPremiumFeatures(userId) {
40 return new Promise(resolve => {
41 setTimeout(() => {
42 resolve({
43 id: userId,
44 premiumFeatures: ['analytics', 'export', 'priority_support'],
45 subscriptionLevel: 'pro'
46 });
47 }, 200);
48 });
49}
50
51// Test with different user types
52const adminUser = { id: 1, name: 'Admin User', role: 'admin' };
53const premiumUser = { id: 2, name: 'Premium User', role: 'premium' };
54const regularUser = { id: 3, name: 'Regular User', role: 'user' };
55
56[adminUser, premiumUser, regularUser].forEach(user => {
57 conditionalProcessing(user)
58 .then(result => {
59 console.log(`Processed ${user.role} user:`, result);
60 });
61});Promise chaining provides a powerful way to compose complex asynchronous workflows while maintaining readable, maintainable code.
Promise composition methods allow you to handle multiple asynchronous operations efficiently. These methods enable parallel execution, race conditions, and sophisticated coordination of async tasks.
Promise.all() - Wait for All Promises:
1// Parallel execution of multiple operations
2function fetchUserProfile(userId) {
3 const userPromise = fetch(`/api/users/${userId}`).then(r => r.json());
4 const postsPromise = fetch(`/api/users/${userId}/posts`).then(r => r.json());
5 const friendsPromise = fetch(`/api/users/${userId}/friends`).then(r => r.json());
6
7 // Wait for all operations to complete
8 return Promise.all([userPromise, postsPromise, friendsPromise])
9 .then(([user, posts, friends]) => {
10 return {
11 user,
12 posts,
13 friends,
14 profileComplete: true
15 };
16 });
17}
18
19// Simulated version for demo
20function simulateFetchUserProfile(userId) {
21 const fetchUser = new Promise(resolve => {
22 setTimeout(() => resolve({ id: userId, name: `User ${userId}` }), 500);
23 });
24
25 const fetchPosts = new Promise(resolve => {
26 setTimeout(() => resolve([
27 { id: 1, title: 'Post 1' },
28 { id: 2, title: 'Post 2' }
29 ]), 800);
30 });
31
32 const fetchFriends = new Promise(resolve => {
33 setTimeout(() => resolve([
34 { id: 101, name: 'Friend 1' },
35 { id: 102, name: 'Friend 2' }
36 ]), 300);
37 });
38
39 return Promise.all([fetchUser, fetchPosts, fetchFriends])
40 .then(([user, posts, friends]) => {
41 console.log("All data fetched simultaneously!");
42 return { user, posts, friends };
43 });
44}
45
46simulateFetchUserProfile(123).then(profile => {
47 console.log("Complete profile:", profile);
48});Promise.allSettled() - Handle Mixed Results:
1// Handle operations that might succeed or fail
2function fetchMultipleResources() {
3 const operations = [
4 fetch('/api/reliable-endpoint').then(r => r.json()),
5 fetch('/api/sometimes-fails').then(r => r.json()),
6 fetch('/api/slow-endpoint').then(r => r.json()),
7 Promise.reject(new Error("Known failing operation"))
8 ];
9
10 return Promise.allSettled(operations)
11 .then(results => {
12 const successful = [];
13 const failed = [];
14
15 results.forEach((result, index) => {
16 if (result.status === 'fulfilled') {
17 successful.push({ index, data: result.value });
18 } else {
19 failed.push({ index, error: result.reason });
20 }
21 });
22
23 return { successful, failed };
24 });
25}
26
27// Simulated version
28function simulateMultipleOperations() {
29 const operations = [
30 new Promise(resolve => setTimeout(() => resolve("Success 1"), 500)),
31 new Promise((_, reject) => setTimeout(() => reject(new Error("Failure 1")), 300)),
32 new Promise(resolve => setTimeout(() => resolve("Success 2"), 700)),
33 Promise.resolve("Immediate success")
34 ];
35
36 return Promise.allSettled(operations)
37 .then(results => {
38 console.log("All operations completed:");
39 results.forEach((result, index) => {
40 if (result.status === 'fulfilled') {
41 console.log(`Operation ${index}: SUCCESS - ${result.value}`);
42 } else {
43 console.log(`Operation ${index}: FAILED - ${result.reason.message}`);
44 }
45 });
46 return results;
47 });
48}
49
50simulateMultipleOperations();Promise.race() - First to Complete:
1// Race conditions and timeouts
2function fetchWithTimeout(url, timeout = 5000) {
3 const fetchPromise = fetch(url).then(response => response.json());
4
5 const timeoutPromise = new Promise((_, reject) => {
6 setTimeout(() => {
7 reject(new Error(`Request timeout after ${timeout}ms`));
8 }, timeout);
9 });
10
11 // Return whichever resolves/rejects first
12 return Promise.race([fetchPromise, timeoutPromise]);
13}
14
15// Simulated version
16function simulateRaceConditions() {
17 const fastOperation = new Promise(resolve => {
18 setTimeout(() => resolve("Fast operation completed"), 1000);
19 });
20
21 const slowOperation = new Promise(resolve => {
22 setTimeout(() => resolve("Slow operation completed"), 3000);
23 });
24
25 const timeoutOperation = new Promise((_, reject) => {
26 setTimeout(() => reject(new Error("Operation timed out")), 2000);
27 });
28
29 console.log("Starting race between operations...");
30
31 return Promise.race([fastOperation, slowOperation, timeoutOperation])
32 .then(result => {
33 console.log("Winner:", result);
34 return result;
35 })
36 .catch(error => {
37 console.log("First to fail:", error.message);
38 throw error;
39 });
40}
41
42simulateRaceConditions();Promise.any() - First Success:
1// Wait for first successful operation
2function tryMultipleEndpoints(data) {
3 const endpoints = [
4 fetch('/api/endpoint1', { method: 'POST', body: JSON.stringify(data) }),
5 fetch('/api/endpoint2', { method: 'POST', body: JSON.stringify(data) }),
6 fetch('/api/endpoint3', { method: 'POST', body: JSON.stringify(data) })
7 ];
8
9 return Promise.any(endpoints)
10 .then(response => {
11 console.log("First successful endpoint responded");
12 return response.json();
13 })
14 .catch(error => {
15 // All endpoints failed
16 console.error("All endpoints failed:", error);
17 throw new Error("All API endpoints are unavailable");
18 });
19}
20
21// Simulated version
22function simulateMultipleEndpoints() {
23 const endpoints = [
24 new Promise((_, reject) =>
25 setTimeout(() => reject(new Error("Endpoint 1 failed")), 1000)
26 ),
27 new Promise(resolve =>
28 setTimeout(() => resolve("Endpoint 2 succeeded"), 1500)
29 ),
30 new Promise(resolve =>
31 setTimeout(() => resolve("Endpoint 3 succeeded"), 2000)
32 )
33 ];
34
35 return Promise.any(endpoints)
36 .then(result => {
37 console.log("First success:", result);
38 return result;
39 })
40 .catch(error => {
41 console.log("All failed:", error.message);
42 throw error;
43 });
44}
45
46simulateMultipleEndpoints();Advanced Composition Patterns:
1// Complex composition: Batch processing with concurrency control
2class PromiseBatchProcessor {
3 constructor(concurrencyLimit = 3) {
4 this.concurrencyLimit = concurrencyLimit;
5 }
6
7 async processBatch(items, processor) {
8 const results = [];
9 const executing = [];
10
11 for (const [index, item] of items.entries()) {
12 const promise = processor(item, index).then(result => ({
13 index,
14 result,
15 status: 'fulfilled'
16 })).catch(error => ({
17 index,
18 error,
19 status: 'rejected'
20 }));
21
22 results.push(promise);
23
24 if (results.length >= this.concurrencyLimit) {
25 executing.push(Promise.race(results));
26
27 // Wait for one to complete before starting next batch
28 await Promise.race(results.map(p => p.catch(() => {})));
29
30 // Remove completed promises
31 const completedIndex = results.findIndex(p => p.isFulfilled || p.isRejected);
32 if (completedIndex !== -1) {
33 results.splice(completedIndex, 1);
34 }
35 }
36 }
37
38 // Wait for all remaining operations
39 return Promise.all(results);
40 }
41}
42
43// Usage example
44const processor = new PromiseBatchProcessor(2); // Limit to 2 concurrent operations
45
46const items = Array.from({ length: 5 }, (_, i) => `Item ${i + 1}`);
47
48const processItem = (item, index) => {
49 return new Promise(resolve => {
50 const delay = Math.random() * 2000 + 500;
51 setTimeout(() => {
52 resolve(`Processed ${item} in ${Math.round(delay)}ms`);
53 }, delay);
54 });
55};
56
57processor.processBatch(items, processItem)
58 .then(results => {
59 console.log("Batch processing completed:");
60 results.forEach(result => {
61 if (result.status === 'fulfilled') {
62 console.log(`ā
${result.result}`);
63 } else {
64 console.log(`ā Error processing item ${result.index}:, result.error.message`);
65 }
66 });
67 });Promise composition methods provide powerful tools for coordinating multiple asynchronous operations efficiently and reliably.
Advanced Promise patterns help you build robust, maintainable asynchronous applications. These patterns address common challenges like retries, circuit breakers, caching, and complex error recovery.
Retry Pattern with Exponential Backoff:
1// Robust retry mechanism for unreliable operations
2class RetryHandler {
3 constructor(options = {}) {
4 this.maxRetries = options.maxRetries || 3;
5 this.baseDelay = options.baseDelay || 1000;
6 this.maxDelay = options.maxDelay || 10000;
7 this.exponentialBase = options.exponentialBase || 2;
8 }
9
10 async execute(operation, ...args) {
11 let lastError;
12
13 for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
14 try {
15 return await operation(...args);
16 } catch (error) {
17 lastError = error;
18
19 if (attempt === this.maxRetries) {
20 break; // Final attempt failed
21 }
22
23 // Calculate delay with exponential backoff
24 const delay = Math.min(
25 this.baseDelay * Math.pow(this.exponentialBase, attempt),
26 this.maxDelay
27 );
28
29 console.log(`Attempt ${attempt + 1} failed: ${error.message}`);
30 console.log(`Retrying in ${delay}ms...`);
31
32 await this.delay(delay);
33 }
34 }
35
36 throw new Error(`Operation failed after ${this.maxRetries + 1} attempts: ${lastError.message}`);
37 }
38
39 delay(ms) {
40 return new Promise(resolve => setTimeout(resolve, ms));
41 }
42}
43
44// Usage example
45const retryHandler = new RetryHandler({
46 maxRetries: 3,
47 baseDelay: 500,
48 exponentialBase: 2
49});
50
51// Unreliable operation that sometimes fails
52function unreliableApiCall() {
53 return new Promise((resolve, reject) => {
54 if (Math.random() < 0.7) { // 70% failure rate
55 reject(new Error("API temporarily unavailable"));
56 } else {
57 resolve({ data: "Success!", timestamp: new Date() });
58 }
59 });
60}
61
62retryHandler.execute(unreliableApiCall)
63 .then(result => {
64 console.log("Operation succeeded:", result);
65 })
66 .catch(error => {
67 console.error("Operation ultimately failed:", error.message);
68 });Circuit Breaker Pattern:
1// Circuit breaker to prevent cascading failures
2class CircuitBreaker {
3 constructor(options = {}) {
4 this.failureThreshold = options.failureThreshold || 5;
5 this.recoveryTimeout = options.recoveryTimeout || 30000;
6 this.monitoringPeriod = options.monitoringPeriod || 10000;
7
8 this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
9 this.failures = 0;
10 this.nextAttempt = 0;
11 this.successCount = 0;
12 }
13
14 async execute(operation, ...args) {
15 if (this.state === 'OPEN') {
16 if (Date.now() < this.nextAttempt) {
17 throw new Error('Circuit breaker is OPEN - operation not attempted');
18 }
19
20 // Transition to HALF_OPEN for testing
21 this.state = 'HALF_OPEN';
22 this.successCount = 0;
23 }
24
25 try {
26 const result = await operation(...args);
27
28 // Operation succeeded
29 if (this.state === 'HALF_OPEN') {
30 this.successCount++;
31
32 // If enough successes, close the circuit
33 if (this.successCount >= Math.ceil(this.failureThreshold / 2)) {
34 this.reset();
35 }
36 } else {
37 this.failures = Math.max(0, this.failures - 1); // Gradual recovery
38 }
39
40 return result;
41 } catch (error) {
42 this.recordFailure();
43 throw error;
44 }
45 }
46
47 recordFailure() {
48 this.failures++;
49
50 if (this.failures >= this.failureThreshold) {
51 this.state = 'OPEN';
52 this.nextAttempt = Date.now() + this.recoveryTimeout;
53 console.log(`Circuit breaker OPENED due to ${this.failures} failures`);
54 }
55 }
56
57 reset() {
58 this.state = 'CLOSED';
59 this.failures = 0;
60 this.nextAttempt = 0;
61 this.successCount = 0;
62 console.log('Circuit breaker CLOSED - normal operation resumed');
63 }
64
65 getState() {
66 return {
67 state: this.state,
68 failures: this.failures,
69 nextAttempt: this.nextAttempt ? new Date(this.nextAttempt) : null
70 };
71 }
72}
73
74// Usage example
75const circuitBreaker = new CircuitBreaker({
76 failureThreshold: 3,
77 recoveryTimeout: 5000
78});
79
80function flakyService() {
81 return new Promise((resolve, reject) => {
82 if (Math.random() < 0.8) { // 80% failure rate for demonstration
83 reject(new Error("Service unavailable"));
84 } else {
85 resolve({ status: "OK", data: "Service response" });
86 }
87 });
88}
89
90// Test the circuit breaker
91async function testCircuitBreaker() {
92 for (let i = 0; i < 10; i++) {
93 try {
94 const result = await circuitBreaker.execute(flakyService);
95 console.log(`Call ${i + 1} succeeded:, result`);
96 } catch (error) {
97 console.log(`Call ${i + 1} failed: ${error.message}`);
98 }
99
100 console.log(`Circuit breaker state:, JSON.stringify(circuitBreaker.getState(), null, 2)`);
101
102 // Wait between calls
103 await new Promise(resolve => setTimeout(resolve, 1000));
104 }
105}
106
107testCircuitBreaker();Promise Caching and Memoization:
1// Smart caching for Promise-based operations
2class PromiseCache {
3 constructor(options = {}) {
4 this.cache = new Map();
5 this.defaultTTL = options.defaultTTL || 300000; // 5 minutes
6 this.maxSize = options.maxSize || 1000;
7 }
8
9 async get(key, factory, ttl = this.defaultTTL) {
10 const cached = this.cache.get(key);
11
12 if (cached && !this.isExpired(cached)) {
13 console.log(`Cache hit for key: ${key}`);
14 return cached.promise;
15 }
16
17 console.log(`Cache miss for key: ${key}`);
18
19 // Create new promise and cache it
20 const promise = this.createCachedPromise(factory, key);
21 const cacheEntry = {
22 promise,
23 timestamp: Date.now(),
24 ttl
25 };
26
27 this.cache.set(key, cacheEntry);
28 this.cleanup();
29
30 return promise;
31 }
32
33 createCachedPromise(factory, key) {
34 return factory().catch(error => {
35 // Remove failed promises from cache
36 console.log(`Removing failed promise for key: ${key}`);
37 this.cache.delete(key);
38 throw error;
39 });
40 }
41
42 isExpired(cacheEntry) {
43 return Date.now() - cacheEntry.timestamp > cacheEntry.ttl;
44 }
45
46 cleanup() {
47 if (this.cache.size <= this.maxSize) return;
48
49 // Remove expired entries
50 for (const [key, entry] of this.cache.entries()) {
51 if (this.isExpired(entry)) {
52 this.cache.delete(key);
53 }
54 }
55
56 // If still over size, remove oldest entries
57 if (this.cache.size > this.maxSize) {
58 const entries = Array.from(this.cache.entries())
59 .sort(([,a], [,b]) => a.timestamp - b.timestamp);
60
61 const toRemove = entries.slice(0, this.cache.size - this.maxSize);
62 toRemove.forEach(([key]) => this.cache.delete(key));
63 }
64 }
65
66 invalidate(key) {
67 return this.cache.delete(key);
68 }
69
70 clear() {
71 this.cache.clear();
72 }
73}
74
75// Usage example
76const cache = new PromiseCache({ defaultTTL: 5000 });
77
78function expensiveOperation(id) {
79 console.log(`Performing expensive operation for ID: ${id}`);
80 return new Promise(resolve => {
81 setTimeout(() => {
82 resolve({
83 id,
84 result: `Processed data for ${id}`,
85 timestamp: new Date()
86 });
87 }, 2000); // Simulate slow operation
88 });
89}
90
91// Cached version
92function cachedExpensiveOperation(id) {
93 return cache.get(`expensive_op_${id}`, () => expensiveOperation(id));
94}
95
96// Test caching behavior
97async function testCaching() {
98 console.log("First call (cache miss):");
99 const result1 = await cachedExpensiveOperation("user123");
100 console.log(result1);
101
102 console.log("\nSecond call (cache hit):");
103 const result2 = await cachedExpensiveOperation("user123");
104 console.log(result2);
105
106 console.log("\nWait for cache expiry...");
107 await new Promise(resolve => setTimeout(resolve, 6000));
108
109 console.log("Third call (cache expired, miss):");
110 const result3 = await cachedExpensiveOperation("user123");
111 console.log(result3);
112}
113
114testCaching();Graceful Degradation Pattern:
1// Graceful degradation with fallback strategies
2class ServiceWithFallbacks {
3 constructor() {
4 this.primaryService = this.createService('primary', 0.7); // 70% success
5 this.secondaryService = this.createService('secondary', 0.9); // 90% success
6 this.cache = new Map();
7 }
8
9 createService(name, successRate) {
10 return (query) => new Promise((resolve, reject) => {
11 setTimeout(() => {
12 if (Math.random() < successRate) {
13 resolve({
14 source: name,
15 data: `Result from ${name} service for: ${query}`,
16 timestamp: new Date()
17 });
18 } else {
19 reject(new Error(`${name} service unavailable`));
20 }
21 }, Math.random() * 1000 + 200);
22 });
23 }
24
25 async getData(query) {
26 const strategies = [
27 () => this.tryPrimaryService(query),
28 () => this.trySecondaryService(query),
29 () => this.tryCache(query),
30 () => this.getStaticFallback(query)
31 ];
32
33 for (const [index, strategy] of strategies.entries()) {
34 try {
35 const result = await strategy();
36 console.log(`Strategy ${index + 1} succeeded:, result.source`);
37
38 // Cache successful results
39 if (result.source !== 'cache' && result.source !== 'static') {
40 this.cache.set(query, result);
41 }
42
43 return result;
44 } catch (error) {
45 console.log(`Strategy ${index + 1} failed: ${error.message}`);
46 if (index === strategies.length - 1) {
47 throw new Error('All fallback strategies exhausted');
48 }
49 }
50 }
51 }
52
53 tryPrimaryService(query) {
54 console.log('Trying primary service...');
55 return this.primaryService(query);
56 }
57
58 trySecondaryService(query) {
59 console.log('Trying secondary service...');
60 return this.secondaryService(query);
61 }
62
63 tryCache(query) {
64 console.log('Trying cache...');
65 const cached = this.cache.get(query);
66 if (cached) {
67 return Promise.resolve({
68 ...cached,
69 source: 'cache'
70 });
71 }
72 return Promise.reject(new Error('No cached data'));
73 }
74
75 getStaticFallback(query) {
76 console.log('Using static fallback...');
77 return Promise.resolve({
78 source: 'static',
79 data: `Static fallback data for: ${query}`,
80 timestamp: new Date(),
81 isStale: true
82 });
83 }
84}
85
86// Usage example
87const service = new ServiceWithFallbacks();
88
89async function testGracefulDegradation() {
90 const queries = ['user-profile', 'dashboard-data', 'notifications'];
91
92 for (const query of queries) {
93 try {
94 console.log(`\nFetching: ${query}`);
95 const result = await service.getData(query);
96 console.log('Final result:', result);
97 } catch (error) {
98 console.error('All strategies failed:', error.message);
99 }
100 }
101}
102
103testGracefulDegradation();These advanced patterns provide robust solutions for common asynchronous programming challenges, ensuring your applications remain reliable under various conditions.
Create a comprehensive data processing system that demonstrates various Promise patterns including sequential processing, parallel execution, error recovery, and sophisticated coordination.
1// === Complete Data Processing Pipeline with Promises ===
2
3// Utility classes for robust Promise-based operations
4class PromiseUtils {
5 // Delay utility
6 static delay(ms) {
7 return new Promise(resolve => setTimeout(resolve, ms));
8 }
9
10 // Timeout wrapper
11 static withTimeout(promise, ms, errorMessage = 'Operation timed out') {
12 const timeoutPromise = this.delay(ms).then(() => {
13 throw new Error(errorMessage);
14 });
15
16 return Promise.race([promise, timeoutPromise]);
17 }
18
19 // Retry with exponential backoff
20 static async retry(operation, options = {}) {
21 const { maxRetries = 3, baseDelay = 1000, exponentialBase = 2 } = options;
22 let lastError;
23
24 for (let attempt = 0; attempt <= maxRetries; attempt++) {
25 try {
26 return await operation();
27 } catch (error) {
28 lastError = error;
29
30 if (attempt === maxRetries) break;
31
32 const delay = baseDelay * Math.pow(exponentialBase, attempt);
33 console.log(`Retry ${attempt + 1} after ${delay}ms: ${error.message}`);
34 await this.delay(delay);
35 }
36 }
37
38 throw new Error(`Failed after ${maxRetries + 1} attempts: ${lastError.message}`);
39 }
40
41 // Batch processing with concurrency control
42 static async batch(items, processor, concurrency = 3) {
43 const results = [];
44 const executing = [];
45
46 for (const item of items) {
47 const promise = processor(item)
48 .then(result => ({ status: 'fulfilled', value: result }))
49 .catch(error => ({ status: 'rejected', reason: error }));
50
51 results.push(promise);
52
53 if (results.length >= concurrency) {
54 executing.push(Promise.race(results));
55 await Promise.race(results);
56 }
57 }
58
59 return Promise.all(results);
60 }
61}
62
63// Data validation and transformation pipeline
64class DataProcessor {
65 constructor() {
66 this.validators = [];
67 this.transformers = [];
68 this.processors = [];
69 }
70
71 // Add validation step
72 addValidator(name, validatorFn) {
73 this.validators.push({ name, fn: validatorFn });
74 return this;
75 }
76
77 // Add transformation step
78 addTransformer(name, transformerFn) {
79 this.transformers.push({ name, fn: transformerFn });
80 return this;
81 }
82
83 // Add processing step
84 addProcessor(name, processorFn) {
85 this.processors.push({ name, fn: processorFn });
86 return this;
87 }
88
89 // Process single item through pipeline
90 async processItem(item) {
91 let result = item;
92 const processingLog = [];
93
94 try {
95 // Validation phase
96 for (const validator of this.validators) {
97 console.log(`Validating with: ${validator.name}`);
98 const isValid = await validator.fn(result);
99 processingLog.push({ step: validator.name, type: 'validation', success: isValid });
100
101 if (!isValid) {
102 throw new Error(`Validation failed: ${validator.name}`);
103 }
104 }
105
106 // Transformation phase
107 for (const transformer of this.transformers) {
108 console.log(`Transforming with: ${transformer.name}`);
109 result = await transformer.fn(result);
110 processingLog.push({ step: transformer.name, type: 'transformation', success: true });
111 }
112
113 // Processing phase
114 for (const processor of this.processors) {
115 console.log(`Processing with: ${processor.name}`);
116 result = await processor.fn(result);
117 processingLog.push({ step: processor.name, type: 'processing', success: true });
118 }
119
120 return {
121 success: true,
122 data: result,
123 processingLog,
124 processedAt: new Date()
125 };
126
127 } catch (error) {
128 return {
129 success: false,
130 error: error.message,
131 originalData: item,
132 processingLog,
133 failedAt: new Date()
134 };
135 }
136 }
137
138 // Process multiple items with different strategies
139 async processSequential(items) {
140 console.log("š Processing items sequentially...");
141 const results = [];
142
143 for (const [index, item] of items.entries()) {
144 console.log(`Processing item ${index + 1}/${items.length}`);
145 const result = await this.processItem(item);
146 results.push(result);
147 }
148
149 return results;
150 }
151
152 async processParallel(items) {
153 console.log("ā” Processing items in parallel...");
154 const promises = items.map((item, index) => {
155 console.log(`Starting parallel processing for item ${index + 1}`);
156 return this.processItem(item);
157 });
158
159 return Promise.all(promises);
160 }
161
162 async processBatch(items, batchSize = 3) {
163 console.log(`š¦ Processing items in batches of ${batchSize}...`);
164 return PromiseUtils.batch(items, (item) => this.processItem(item), batchSize);
165 }
166
167 // Process with error resilience
168 async processResilient(items) {
169 console.log("š”ļø Processing with error resilience...");
170 return Promise.allSettled(
171 items.map(item => this.processItem(item))
172 ).then(results => {
173 const successful = results
174 .filter(r => r.status === 'fulfilled')
175 .map(r => r.value);
176
177 const failed = results
178 .filter(r => r.status === 'rejected')
179 .map(r => ({ error: r.reason.message }));
180
181 return { successful, failed };
182 });
183 }
184}
185
186// External service simulators
187class ExternalServices {
188 // Simulate user data enrichment service
189 static enrichUserData(userData) {
190 return new Promise((resolve, reject) => {
191 setTimeout(() => {
192 if (Math.random() < 0.1) { // 10% failure rate
193 reject(new Error("User enrichment service unavailable"));
194 return;
195 }
196
197 resolve({
198 ...userData,
199 enriched: true,
200 profile: {
201 tier: userData.email.includes('premium') ? 'premium' : 'standard',
202 reputation: Math.floor(Math.random() * 100),
203 joinedAt: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000)
204 },
205 preferences: {
206 notifications: true,
207 theme: 'light',
208 language: 'en'
209 }
210 });
211 }, 300 + Math.random() * 500);
212 });
213 }
214
215 // Simulate database save operation
216 static saveToDatabase(data) {
217 return new Promise((resolve, reject) => {
218 setTimeout(() => {
219 if (Math.random() < 0.05) { // 5% failure rate
220 reject(new Error("Database connection failed"));
221 return;
222 }
223
224 resolve({
225 ...data,
226 id: 'id_' + Math.random().toString(36).substr(2, 9),
227 savedAt: new Date(),
228 version: 1
229 });
230 }, 200 + Math.random() * 300);
231 });
232 }
233
234 // Simulate notification service
235 static sendNotification(userData) {
236 return new Promise((resolve, reject) => {
237 setTimeout(() => {
238 if (Math.random() < 0.15) { // 15% failure rate
239 reject(new Error("Notification service unavailable"));
240 return;
241 }
242
243 resolve({
244 notificationId: 'notif_' + Math.random().toString(36).substr(2, 9),
245 recipient: userData.email,
246 message: `Welcome ${userData.name}! Your account has been created.`,
247 sentAt: new Date()
248 });
249 }, 400 + Math.random() * 600);
250 });
251 }
252}
253
254// Analytics and monitoring
255class ProcessingAnalytics {
256 constructor() {
257 this.metrics = {
258 totalProcessed: 0,
259 successful: 0,
260 failed: 0,
261 averageProcessingTime: 0,
262 processingTimes: []
263 };
264 }
265
266 startTiming() {
267 return Date.now();
268 }
269
270 recordResult(startTime, success) {
271 const processingTime = Date.now() - startTime;
272 this.metrics.processingTimes.push(processingTime);
273 this.metrics.totalProcessed++;
274
275 if (success) {
276 this.metrics.successful++;
277 } else {
278 this.metrics.failed++;
279 }
280
281 this.metrics.averageProcessingTime =
282 this.metrics.processingTimes.reduce((a, b) => a + b, 0) /
283 this.metrics.processingTimes.length;
284 }
285
286 getReport() {
287 return {
288 ...this.metrics,
289 successRate: (this.metrics.successful / this.metrics.totalProcessed * 100).toFixed(2) + '%',
290 failureRate: (this.metrics.failed / this.metrics.totalProcessed * 100).toFixed(2) + '%'
291 };
292 }
293}
294
295// Main application orchestrating the pipeline
296class DataProcessingApp {
297 constructor() {
298 this.processor = new DataProcessor();
299 this.analytics = new ProcessingAnalytics();
300 this.setupPipeline();
301 }
302
303 setupPipeline() {
304 // Validation steps
305 this.processor
306 .addValidator('required-fields', async (data) => {
307 await PromiseUtils.delay(50); // Simulate async validation
308 return data.name && data.email && data.age;
309 })
310 .addValidator('email-format', async (data) => {
311 await PromiseUtils.delay(30);
312 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email);
313 })
314 .addValidator('age-range', async (data) => {
315 await PromiseUtils.delay(20);
316 return data.age >= 13 && data.age <= 120;
317 });
318
319 // Transformation steps
320 this.processor
321 .addTransformer('normalize-data', async (data) => {
322 await PromiseUtils.delay(100);
323 return {
324 ...data,
325 name: data.name.trim(),
326 email: data.email.toLowerCase(),
327 age: parseInt(data.age, 10),
328 createdAt: new Date()
329 };
330 })
331 .addTransformer('add-metadata', async (data) => {
332 await PromiseUtils.delay(80);
333 return {
334 ...data,
335 metadata: {
336 source: 'user-registration',
337 processedBy: 'data-pipeline-v2',
338 fingerprint: btoa(data.name + data.email).substr(0, 16)
339 }
340 };
341 });
342
343 // Processing steps with external service calls
344 this.processor
345 .addProcessor('enrich-user-data', async (data) => {
346 return PromiseUtils.withTimeout(
347 PromiseUtils.retry(
348 () => ExternalServices.enrichUserData(data),
349 { maxRetries: 2, baseDelay: 500 }
350 ),
351 5000,
352 'User enrichment timeout'
353 );
354 })
355 .addProcessor('save-to-database', async (data) => {
356 return PromiseUtils.withTimeout(
357 PromiseUtils.retry(
358 () => ExternalServices.saveToDatabase(data),
359 { maxRetries: 3, baseDelay: 200 }
360 ),
361 3000,
362 'Database save timeout'
363 );
364 })
365 .addProcessor('send-notification', async (data) => {
366 // Non-blocking notification - don't fail the pipeline if this fails
367 try {
368 const notification = await PromiseUtils.withTimeout(
369 ExternalServices.sendNotification(data),
370 2000,
371 'Notification timeout'
372 );
373 return { ...data, notification };
374 } catch (error) {
375 console.warn('Notification failed (non-blocking):', error.message);
376 return { ...data, notificationFailed: true };
377 }
378 });
379 }
380
381 // Process data with comprehensive monitoring
382 async processData(items, strategy = 'parallel') {
383 const startTime = this.analytics.startTiming();
384
385 console.log(`\nš Starting data processing with ${strategy} strategy`);
386 console.log(`Processing ${items.length} items...`);
387
388 let results;
389
390 try {
391 switch (strategy) {
392 case 'sequential':
393 results = await this.processor.processSequential(items);
394 break;
395 case 'parallel':
396 results = await this.processor.processParallel(items);
397 break;
398 case 'batch':
399 results = await this.processor.processBatch(items, 2);
400 break;
401 case 'resilient':
402 results = await this.processor.processResilient(items);
403 break;
404 default:
405 throw new Error('Unknown processing strategy');
406 }
407
408 this.recordResults(results, startTime);
409 return results;
410
411 } catch (error) {
412 console.error('Processing pipeline failed:', error.message);
413 this.analytics.recordResult(startTime, false);
414 throw error;
415 }
416 }
417
418 recordResults(results, startTime) {
419 // Handle different result formats
420 if (Array.isArray(results)) {
421 results.forEach(result => {
422 const success = result.status === 'fulfilled' ?
423 result.value?.success : result.success;
424 this.analytics.recordResult(startTime, success);
425 });
426 } else if (results.successful && results.failed) {
427 // Resilient processing results
428 results.successful.forEach(() => {
429 this.analytics.recordResult(startTime, true);
430 });
431 results.failed.forEach(() => {
432 this.analytics.recordResult(startTime, false);
433 });
434 }
435 }
436
437 printResults(results, strategy) {
438 console.log(`\nš Processing Results (${strategy}):`);
439
440 if (Array.isArray(results)) {
441 const successful = results.filter(r =>
442 r.status === 'fulfilled' ? r.value?.success : r.success
443 );
444 const failed = results.filter(r =>
445 r.status === 'rejected' || !r.success
446 );
447
448 console.log(`ā
Successful: ${successful.length}`);
449 console.log(`ā Failed: ${failed.length}`);
450
451 failed.forEach((result, index) => {
452 const error = result.status === 'rejected' ?
453 result.reason?.message : result.error;
454 console.log(` ${index + 1}. ${error}`);
455 });
456
457 } else if (results.successful && results.failed) {
458 console.log(`ā
Successful: ${results.successful.length}`);
459 console.log(`ā Failed: ${results.failed.length}`);
460 }
461
462 console.log('\nš Analytics:', this.analytics.getReport());
463 }
464}
465
466// Sample data for testing
467const sampleData = [
468 {
469 name: "Alice Johnson",
470 email: "[email protected]",
471 age: "28"
472 },
473 {
474 name: "Bob Smith",
475 email: "[email protected]",
476 age: "35"
477 },
478 {
479 name: "Charlie Brown",
480 email: "charlie@invalid-email", // Invalid email
481 age: "22"
482 },
483 {
484 name: "", // Missing name
485 email: "[email protected]",
486 age: "30"
487 },
488 {
489 name: "Diana Prince",
490 email: "[email protected]",
491 age: "150" // Invalid age
492 },
493 {
494 name: "Eve Wilson",
495 email: "[email protected]",
496 age: "26"
497 }
498];
499
500// Application demonstration
501async function demonstrateDataProcessing() {
502 const app = new DataProcessingApp();
503
504 const strategies = ['parallel', 'sequential', 'batch', 'resilient'];
505
506 for (const strategy of strategies) {
507 try {
508 console.log(`\n${'='.repeat(60)}`);
509 console.log(`Testing ${strategy.toUpperCase()} processing strategy`);
510 console.log(`${'='.repeat(60)}`);
511
512 const results = await app.processData([...sampleData], strategy);
513 app.printResults(results, strategy);
514
515 // Wait between strategies for clarity
516 await PromiseUtils.delay(1000);
517
518 } catch (error) {
519 console.error(`Strategy ${strategy} failed:, error.message`);
520 }
521 }
522
523 console.log(`\n${'='.repeat(60)}`);
524 console.log('Final Analytics Report');
525 console.log(`${'='.repeat(60)}`);
526 console.log(JSON.stringify(app.analytics.getReport(), null, 2));
527}
528
529// Run the demonstration
530demonstrateDataProcessing().then(() => {
531 console.log('\nā
Data processing pipeline demonstration completed!');
532}).catch(error => {
533 console.error('ā Demonstration failed:', error);
534});
535
536/*
537šÆ Key Promise Patterns Demonstrated:
538
5391. **Sequential Processing**:
540 - Operations execute one after another
541 - Useful for dependent operations or rate limiting
542
5432. **Parallel Processing**:
544 - All operations start simultaneously
545 - Maximum throughput but higher resource usage
546
5473. **Batch Processing**:
548 - Controlled concurrency with batch limits
549 - Balance between throughput and resource management
550
5514. **Resilient Processing**:
552 - Promise.allSettled for handling mixed success/failure
553 - Operations continue even if some fail
554
5555. **Error Handling Strategies**:
556 - Retry with exponential backoff
557 - Timeouts to prevent hanging operations
558 - Graceful degradation and fallback handling
559
5606. **Composition Patterns**:
561 - Promise chaining for sequential transformations
562 - Promise.all for parallel coordination
563 - Promise.race for timeout implementations
564
5657. **Real-world Integration**:
566 - External service simulation with realistic failure rates
567 - Comprehensive analytics and monitoring
568 - Flexible pipeline configuration
569
570This demonstrates how Promises enable building sophisticated,
571reliable asynchronous processing systems that can handle
572complex real-world requirements with proper error handling
573and performance optimization.
574*/Your progress is saved in this browser, on this device only.