Back to Basics: Advanced Promise Patterns in JavaScript
Building on our ‘Back to Basics’ series, this article delves into the advanced patterns of Promises in JavaScript. Promises are a fundamental part of asynchronous programming in JavaScript, providing a more manageable approach to handling asynchronous operations. While basic promise usage is relatively straightforward, understanding advanced patterns is key to writing efficient, clean, and robust JavaScript code. We will explore several advanced promise patterns, each with practical examples to illustrate their use in real-world scenarios.

1. Promise.all()
Promise.all() is ideal when you have multiple asynchronous operations that are independent of each other and need to be performed concurrently.
const getUser = id => Promise.resolve(`User ${id}`);
const getOrder = id => Promise.resolve(`Order ${id}`);
Promise.all([getUser(1), getOrder(123)])
.then(([user, order]) => console.log(user, order))
.catch(error => console.error(error));This example demonstrates fetching user and order data simultaneously, with Promise.all() ensuring both promises resolve before proceeding.
Example
Loading user profile and recent activity data simultaneously for a social media dashboard.
const fetchUserProfile = userId => fetch(`https://api.socialmedia.com/users/${userId}`).then(res => res.json());
const fetchUserActivity = userId => fetch(`https://api.socialmedia.com/users/${userId}/activity`).then(res => res.json());
Promise.all([fetchUserProfile('user123'), fetchUserActivity('user123')])
.then(([profile, activity]) => {
// Update UI with profile and activity data
updateProfileUI(profile);
updateActivityFeed(activity);
})
.catch(error => console.error('Error fetching user data:', error));2. Promise.race()
Use Promise.race() when you need to respond to whichever promise resolves or rejects first. It's useful in scenarios like timeout implementations.
const timeout = ms => new Promise((_, reject) => setTimeout(() => reject('Timed out'), ms));
const fetchData = () => fetch('https://api.example.com/data').then(response => response.json());
Promise.race([fetchData(), timeout(5000)])
.then(data => console.log(data))
.catch(error => console.error(error));Here, Promise.race() is used to fetch data with a 5-second timeout, handling whichever occurs first.
Example
Implementing a timeout for a file download to avoid long waiting times.
const downloadFile = fileUrl => fetch(fileUrl).then(response => response.blob());
const timeout = ms => new Promise((_, reject) => setTimeout(() => reject('Download timed out'), ms));
Promise.race([downloadFile('https://api.files.com/myfile.zip'), timeout(10000)])
.then(fileBlob => saveFile(fileBlob))
.catch(error => console.error(error));3. Promise.allSettled()
Promise.allSettled() is useful when you need to wait for all promises to settle, regardless of whether they resolve or reject.
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'error'));
Promise.allSettled([promise1, promise2])
.then(results => results.forEach((result) => console.log(result.status)))
.catch(error => console.error(error));This pattern is great for scenarios where you need the results of all operations, successful or not.
Example
Performing multiple API updates where each update’s success or failure should be handled individually.
const updateEmail = email => fetch('https://api.user.com/updateEmail', { /* ... */ });
const updateProfilePicture = picture => fetch('https://api.user.com/updateProfilePicture', { /* ... */ });
Promise.allSettled([updateEmail('[email protected]'), updateProfilePicture(imageBlob)])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Update successful');
} else {
console.error('Update failed:', result.reason);
}
});
});4. Promise.any()
Promise.any() returns the first promise that resolves and ignores rejections unless all promises reject.
const promise1 = new Promise((resolve, reject) => setTimeout(reject, 500, 'Error'));
const promise2 = Promise.resolve('First resolved');
Promise.any([promise1, promise2])
.then(value => console.log(value))
.catch(error => console.error(error));This is beneficial for cases where the first successful result is needed, and others can be safely ignored.
Example
Retrieving a resource from multiple mirrors or endpoints, using the first successful response.
const fetchFromMirror1 = () => fetch('https://mirror1.api.com/resource');
const fetchFromMirror2 = () => fetch('https://mirror2.api.com/resource');
const fetchFromMirror3 = () => fetch('https://mirror3.api.com/resource');
Promise.any([fetchFromMirror1(), fetchFromMirror2(), fetchFromMirror3()])
.then(response => response.json())
.then(data => {
// Process the data from the first successful mirror
processData(data);
})
.catch(error => console.error('Unable to fetch from any mirror:', error));Mastering these advanced promise patterns in JavaScript allows developers to handle complex asynchronous scenarios more effectively. Each pattern offers unique advantages and can be selected based on the specific requirements of the task at hand. Understanding these patterns is crucial for writing advanced JavaScript applications that are both reliable and efficient.
You might also be interested in other articles from this series:






