Introduction to Promise Static Methods
While we've explored creating individual Promises and chaining them, the Promise API also provides powerful static methods that operate on collections of promises or simplify common promise patterns. These methods allow us to coordinate multiple asynchronous operations in sophisticated ways.
Static methods are called directly on the Promise constructor itself, not on Promise instances. They help us coordinate multiple promises, transform values into promises, or create pre-resolved promises.
Creating Pre-Resolved Promises
Promise.resolve()
Promise.resolve() creates a Promise that is already resolved with a given value. This is useful when you need to convert a synchronous value into a Promise, ensuring consistency in your API.
Real-World Analogy
Think of Promise.resolve() as sending a pre-signed contract. Instead of waiting for negotiations and signatures, you're providing something that's already finalized and ready to use.
// Creating a resolved promise
const resolvedPromise = Promise.resolve('Data is ready');
// Using the pre-resolved promise
resolvedPromise.then(data => {
console.log(data); // Outputs: "Data is ready"
});
// Practical example: Creating an API that always returns promises
function fetchUserData(userId) {
const cachedData = checkCache(userId);
if (cachedData) {
// Return a pre-resolved promise with cached data
return Promise.resolve(cachedData);
}
// Otherwise, make the actual API call which returns a promise
return makeAPICall(userId);
}
Promise.reject()
Similarly, Promise.reject() creates a Promise that is already rejected with a given reason. This is useful for error handling and creating predictable failure paths.
// Creating a rejected promise
const rejectedPromise = Promise.reject(new Error('Access denied'));
// Handling the rejection
rejectedPromise
.catch(error => {
console.error(error.message); // Outputs: "Access denied"
});
// Practical example: Function that validates input before proceeding
function processUserInput(input) {
if (!input) {
// Return early with a rejected promise
return Promise.reject(new Error('Input cannot be empty'));
}
// Process the valid input asynchronously
return asyncProcessing(input);
}
Coordinating Multiple Promises
Promise.all()
Promise.all() takes an iterable of promises and returns a new promise that resolves when all input promises have resolved, or rejects if any of the promises reject.
The resolved value is an array of all the fulfillment values, in the same order as the promises provided.
Real-World Analogy
Promise.all() is like coordinating a dinner party where you need all ingredients to arrive before you can start cooking. If even one ingredient doesn't arrive (rejects), dinner is canceled.
// Example: Loading multiple resources in parallel
const fetchUserProfile = fetch('/api/user').then(res => res.json());
const fetchUserPosts = fetch('/api/posts').then(res => res.json());
const fetchUserAnalytics = fetch('/api/analytics').then(res => res.json());
// Wait for all data to be available before rendering the dashboard
Promise.all([fetchUserProfile, fetchUserPosts, fetchUserAnalytics])
.then(([profile, posts, analytics]) => {
renderDashboard(profile, posts, analytics);
})
.catch(error => {
showErrorMessage('Failed to load dashboard data');
console.error(error);
});
// Real-world example: E-commerce checkout process
function processCheckout(cartId) {
// These operations can happen in parallel
const validateInventory = checkInventoryLevels(cartId);
const processPayment = chargeCustomerCard(cartId);
const reserveShipping = scheduleDelivery(cartId);
return Promise.all([validateInventory, processPayment, reserveShipping])
.then(([inventory, payment, shipping]) => {
return {
orderId: generateOrderId(),
paymentId: payment.id,
shippingTrackingCode: shipping.trackingCode,
items: inventory.items
};
})
.catch(error => {
// If any step fails, the entire checkout fails
cancelPendingOperations(cartId);
throw new Error(`Checkout failed: ${error.message}`);
});
}
Promise.race()
Promise.race() takes an iterable of promises and returns a new promise that resolves or rejects as soon as one of the promises resolves or rejects, with the value or reason from that promise.
Real-World Analogy
Promise.race() is like a competition where the first contestant to finish determines the result. Whether they win or lose (resolve or reject), their result becomes the final outcome.
// Example: Race between fetching data and a timeout
function fetchWithTimeout(url, timeout = 5000) {
// Create a promise that rejects after a timeout
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Request to ${url} timed out after ${timeout}ms`));
}, timeout);
});
// Race between the fetch and the timeout
return Promise.race([
fetch(url).then(response => response.json()),
timeoutPromise
]);
}
// Using the function
fetchWithTimeout('/api/data', 3000)
.then(data => {
console.log('Data retrieved successfully:', data);
})
.catch(error => {
console.error('Failed to fetch data:', error.message);
});
// Real-world example: Fallback servers for high-availability
function fetchFromMirrors(resource) {
const primaryServer = fetch(`https://primary.example.com/${resource}`);
const backupServer = fetch(`https://backup.example.com/${resource}`);
const failoverServer = fetch(`https://failover.example.com/${resource}`);
// Return the first successful response
return Promise.race([primaryServer, backupServer, failoverServer]);
}
Modern Promise Combinators
ES2020 and ES2021 added two important new static methods to the Promise API that give developers more control and flexibility when working with multiple promises.
Promise.allSettled() (ES2020)
Promise.allSettled() takes an iterable of promises and returns a new promise that resolves after all promises have settled (either resolved or rejected). The resolved value is an array of objects describing the outcome of each promise.
Real-World Analogy
Promise.allSettled() is like sending multiple scouts to explore different paths. You wait for all of them to return regardless of what they found, then analyze all the reports together.
// Example: Attempt to fetch multiple resources and report all results
const endpoints = [
'https://api.example.com/users',
'https://api.example.com/invalid-endpoint', // Will 404
'https://api.example.com/products'
];
const requests = endpoints.map(url =>
fetch(url)
.then(response => response.json())
.catch(error => ({ error: error.message }))
);
Promise.allSettled(requests)
.then(results => {
// Process all results, including errors
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Endpoint ${endpoints[index]} succeeded:`, result.value);
} else {
console.log(`Endpoint ${endpoints[index]} failed:`, result.reason);
}
});
// Continue with whatever data we successfully retrieved
const successfulResults = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
processAvailableData(successfulResults);
});
// Real-world example: Batch operations with partial success
function batchProcessUsers(userIds) {
const processingOperations = userIds.map(id =>
processUserData(id)
.then(result => ({ status: 'fulfilled', userId: id, result }))
.catch(error => ({ status: 'rejected', userId: id, error }))
);
return Promise.allSettled(processingOperations)
.then(results => {
// Generate a comprehensive report
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
return {
totalProcessed: userIds.length,
successCount: successful.length,
failureCount: failed.length,
successfulIds: successful.map(r => r.userId),
failedIds: failed.map(r => r.userId),
failures: failed.map(r => ({ id: r.userId, reason: r.error.message }))
};
});
}
Promise.any() (ES2021)
Promise.any() takes an iterable of promises and returns a new promise that resolves as soon as one of the promises resolves. If all promises reject, it rejects with an AggregateError containing all rejection reasons.
Real-World Analogy
Promise.any() is like a search party looking for something - you need just one person to find it for the mission to succeed. Only if everyone fails do you declare the mission a failure.
successful value"] B -->|No| D["Reject with
AggregateError"] style A fill:#d0e8ff,stroke:#333,stroke-width:1px style B fill:#ffe0b2,stroke:#333,stroke-width:1px style C fill:#c8e6c9,stroke:#333,stroke-width:1px style D fill:#ffcdd2,stroke:#333,stroke-width:1px
// Example: Try multiple image CDNs until one works
function loadImageFromMirrors(filename) {
const mirrors = [
`https://cdn1.example.com/images/${filename}`,
`https://cdn2.example.com/images/${filename}`,
`https://cdn3.example.com/images/${filename}`
];
const imagePromises = mirrors.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = () => reject(`Failed to load from ${url}`);
img.src = url;
});
});
return Promise.any(imagePromises)
.then(successfulUrl => {
console.log(`Image loaded from: ${successfulUrl}`);
return successfulUrl;
})
.catch(errors => {
// AggregateError contains all the individual errors
console.error('All image sources failed');
throw new Error('Could not load image from any source');
});
}
// Real-world example: Authentication with multiple providers
function authenticateUser(credentials) {
// Try multiple auth services in parallel
const primaryAuth = authenticateWithPrimary(credentials);
const legacyAuth = authenticateWithLegacy(credentials);
const federatedAuth = authenticateWithFederated(credentials);
return Promise.any([primaryAuth, legacyAuth, federatedAuth])
.then(authResult => {
// Use the first successful authentication
return {
authenticated: true,
token: authResult.token,
provider: authResult.provider
};
})
.catch(errors => {
// All authentication methods failed
console.error('Authentication failed with all providers');
return {
authenticated: false,
reason: 'All authentication methods failed'
};
});
}
Comparison of Promise Combinators
Let's compare the behavior of the different Promise combinators with a consistent example to highlight their differences.
| Method | Success Condition | Failure Condition | Result Value | Best Use Case |
|---|---|---|---|---|
Promise.all() |
All promises resolve | Any promise rejects | Array of all resolved values | When you need all operations to succeed |
Promise.race() |
First promise settles | First promise settles (if rejected) | Value/reason of first settled promise | Timeouts, cancelation, first-response-wins |
Promise.allSettled() |
Always succeeds | Never fails | Array of result objects with status | When you need results from all operations regardless of success/failure |
Promise.any() |
Any promise resolves | All promises reject | First resolved value | When you need at least one operation to succeed |
// Example to show the differences
const promises = [
Promise.resolve('Success 1'),
Promise.reject('Error 1'),
new Promise(resolve => setTimeout(() => resolve('Success 2'), 1000)),
new Promise((_, reject) => setTimeout(() => reject('Error 2'), 1500))
];
// Promise.all
Promise.all(promises)
.then(results => console.log('all success:', results))
.catch(error => console.log('all error:', error));
// Output: "all error: Error 1" (fails fast)
// Promise.race
Promise.race(promises)
.then(result => console.log('race success:', result))
.catch(error => console.log('race error:', error));
// Output: "race success: Success 1" (first promise resolves)
// Promise.allSettled
Promise.allSettled(promises)
.then(results => console.log('allSettled:', results));
// Output: "allSettled: [
// {status: 'fulfilled', value: 'Success 1'},
// {status: 'rejected', reason: 'Error 1'},
// {status: 'fulfilled', value: 'Success 2'},
// {status: 'rejected', reason: 'Error 2'}
// ]"
// Promise.any
Promise.any(promises)
.then(result => console.log('any success:', result))
.catch(errors => console.log('any error:', errors));
// Output: "any success: Success 1" (succeeds with first success)
Practical Applications & Patterns
Sequential vs. Parallel Promise Execution
Understanding when to use sequential promise execution (one after another) versus parallel execution (all at once) is crucial for performance and correctness.
// Sequential execution (when order matters)
async function processFilesSequentially(fileIds) {
const results = [];
for (const id of fileIds) {
// Each operation waits for the previous one
const file = await fetchFile(id);
const processed = await processFile(file);
results.push(processed);
}
return results;
}
// Parallel execution (when operations can happen simultaneously)
async function processFilesInParallel(fileIds) {
// Create all promises at once
const fetchPromises = fileIds.map(id => fetchFile(id));
// Wait for all fetches to complete
const files = await Promise.all(fetchPromises);
// Process all files in parallel
const processPromises = files.map(file => processFile(file));
// Wait for all processing to complete
return Promise.all(processPromises);
}
Controlling Concurrency
Sometimes you want to run operations in parallel, but with a limit on how many can run simultaneously to avoid overwhelming resources.
// Function to process items with limited concurrency
async function processWithConcurrencyLimit(items, concurrencyLimit, processItem) {
const results = [];
const running = new Set();
// Process all items
for (const item of items) {
// Create a promise for this item
const promise = processItem(item).then(result => {
running.delete(promise);
return result;
});
// Add it to the set of running promises
running.add(promise);
results.push(promise);
// If we've hit the concurrency limit, wait for one to finish
if (running.size >= concurrencyLimit) {
await Promise.race(running);
}
}
// Wait for all remaining promises to settle
return Promise.all(results);
}
// Usage example: Process 100 images with max 5 concurrent operations
const imageIds = Array.from({ length: 100 }, (_, i) => `img_${i}`);
processWithConcurrencyLimit(imageIds, 5, async (imageId) => {
const image = await fetchImage(imageId);
const processed = await applyFilters(image);
return processed;
}).then(results => {
console.log(`Processed ${results.length} images`);
});
Error Recovery Strategies
Using Promise combinators effectively for different error handling scenarios is a key skill for robust asynchronous code.
// Retry a failed operation with exponential backoff
async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 300) {
let retries = 0;
while (true) {
try {
return await operation();
} catch (error) {
if (retries >= maxRetries) {
throw error; // Max retries reached, propagate the error
}
// Calculate delay with exponential backoff and jitter
const delay = baseDelay * Math.pow(2, retries) + Math.random() * 100;
console.log(`Retry ${retries + 1} after ${delay.toFixed(0)}ms`);
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delay));
retries++;
}
}
}
// Usage example
retryWithBackoff(() => fetchUserData(userId))
.then(data => {
console.log('Successfully retrieved user data:', data);
})
.catch(error => {
console.error('Failed after multiple retries:', error);
});
// Fallback chain with multiple options
function fetchWithFallbacks(resourceId) {
const primaryPromise = fetchFromPrimary(resourceId);
const cachePromise = primaryPromise.catch(() => fetchFromCache(resourceId));
const backupPromise = cachePromise.catch(() => fetchFromBackup(resourceId));
const defaultPromise = backupPromise.catch(() => getDefaultResource(resourceId));
return defaultPromise;
}
Browser Support and Polyfills
Modern browsers support all Promise static methods, but some older browsers may not support the newer methods like Promise.allSettled() or Promise.any().
For older browsers, you can use polyfills or implement these methods yourself:
// Simple polyfill for Promise.allSettled
if (!Promise.allSettled) {
Promise.allSettled = function(promises) {
return Promise.all(
promises.map(p =>
Promise.resolve(p)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
)
);
};
}
// Simple polyfill for Promise.any
if (!Promise.any) {
Promise.any = function(promises) {
return new Promise((resolve, reject) => {
let errors = [];
let rejectedCount = 0;
const totalCount = promises.length;
if (totalCount === 0) {
reject(new AggregateError([], 'No promises to resolve'));
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
resolve(value); // Resolve with the first success
},
error => {
errors[index] = error;
rejectedCount++;
// If all promises rejected, reject with AggregateError
if (rejectedCount === totalCount) {
reject(new AggregateError(errors, 'All promises rejected'));
}
}
);
});
});
};
}
Best Practices & Common Pitfalls
Best Practices
- Choose the right combinator: Select the appropriate Promise static method based on your specific requirements.
- Always handle errors: Attach error handlers to Promise chains to avoid unhandled rejection warnings.
- Consider response time variations: Be aware that network responses can vary in timing, which can affect methods like
Promise.race(). - Avoid unnecessary nesting: Use combinators to flatten complex promise structures.
Common Pitfalls
- Empty arrays to Promise.all(): Passing an empty array to
Promise.all()resolves immediately with an empty array. - Memory leaks: Unresolved promises can cause memory leaks if they retain references to large objects.
- Not considering error propagation: With
Promise.all(), a single rejection causes the entire operation to fail. - Overlooking returned promises: Forgetting to return promises in
.then()handlers breaks the chain.
// Pitfall: Not returning promises in chain
fetchUser(userId)
.then(user => {
// Missing return, breaks the chain!
processUserData(user);
})
.then(processedData => {
// processedData will be undefined!
displayUserProfile(processedData);
});
// Correct version
fetchUser(userId)
.then(user => {
// Properly return the next promise
return processUserData(user);
})
.then(processedData => {
displayUserProfile(processedData);
});
// Pitfall: Not considering the behavior of Promise.all with errors
Promise.all([reliable(), mightFail(), alsoReliable()])
.then(results => {
// If mightFail() rejects, this never executes
// and alsoReliable()'s result is lost
processAllResults(results);
});
// Better approach for operations that might fail
Promise.allSettled([reliable(), mightFail(), alsoReliable()])
.then(results => {
// Process all results, handling failures individually
const successResults = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
processAvailableResults(successResults);
});
Practice Exercises
Exercise 1: Sequential Data Processing
Create a function that processes an array of user IDs sequentially, fetching each user's data and processing it before moving to the next. Use Promise.resolve() to handle both synchronous and asynchronous operations.
Exercise 2: Parallel Data Fetching with Timeout
Implement a function that fetches data from multiple APIs in parallel but adds a timeout for each request. If any request takes too long, it should be canceled and replaced with a default value.
Exercise 3: Race Condition Handling
Create a function that loads an image from multiple CDNs using Promise.any(). Add proper error handling for cases where all CDNs fail.
Exercise 4: Batch Processing with Status Reporting
Implement a batch processing system that processes items in small batches to avoid overloading resources. Use Promise.allSettled() to report on the success or failure of each item.
Summary
Promise static methods provide powerful tools for coordinating asynchronous operations in JavaScript:
- Promise.resolve()/Promise.reject(): Create pre-resolved or pre-rejected promises
- Promise.all(): Wait for all promises to succeed; fails if any fail
- Promise.race(): Resolves/rejects with the first settled promise
- Promise.allSettled(): Wait for all promises to settle; never rejects
- Promise.any(): Resolves with the first success; rejects only if all fail
By understanding these methods and their use cases, you can create more efficient, resilient, and maintainable asynchronous code. Modern JavaScript applications frequently use these techniques to handle complex workflows involving multiple data sources, parallel operations, and error recovery mechanisms.