Promise.race() is a static Promise method that takes an array of promises and returns a new promise that resolves or rejects with the result of the first completed promise from the array. It’s useful for implementing timeouts, handling races between multiple asynchronous operations, and selecting data from the fastest source.
Main use cases:
“Winner takes all!” 🏁
const promise1 = new Promise(resolve =>
setTimeout(() => resolve('Winner 1'), 500)
);
const promise2 = new Promise(resolve =>
setTimeout(() => resolve('Winner 2'), 200)
);
const promise3 = new Promise((resolve, reject) =>
setTimeout(() => reject('Error!'), 300)
);
Promise.race([promise1, promise2, promise3])
.then(result => console.log('Winner:', result)) // "Winner: Winner 2"
.catch(error => console.error('Failed:', error));Promise.race() is a powerful tool in JavaScript asynchronous programming that allows executing multiple promises in parallel and working with the result of the first completed one. Unlike Promise.all(), which waits for all promises to complete, Promise.race() responds to the first completed promise.
Promise.race() takes an iterable object (usually an array) of promises and returns a new promise:
const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 2000));
const promise3 = new Promise(resolve => setTimeout(() => resolve(3), 500));
Promise.race([promise1, promise2, promise3])
.then(result => console.log(result)); // 3 (fastest)One of the most common uses of Promise.race() is adding timeouts to asynchronous operations:
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
});
return Promise.race([promise, timeout]);
}
// Usage
withTimeout(fetch('/api/data'), 5000)
.then(response => console.log('Data received'))
.catch(error => {
if (error.message === 'Timeout') {
console.log('Request exceeded time limit');
} else {
console.error('Request error:', error);
}
});Useful when having multiple sources of the same data:
function fetchFromMultipleSources(url) {
const sources = [
fetch(`https://fast-cdn.com${url}`),
fetch(`https://backup-server.com${url}`),
fetch(`https://mirror-site.com${url}`)
];
return Promise.race(sources);
}
// Usage
fetchFromMultipleSources('/api/user-data')
.then(response => response.json())
.then(data => console.log('Data received from fastest source:', data));Can be used to cancel operations on first signal:
function cancellableOperation(operationPromise, cancelSignal) {
return Promise.race([
operationPromise,
cancelSignal.then(() => Promise.reject(new Error('Operation cancelled')))
]);
}
// Usage
const longOperation = fetch('/api/long-process');
const cancelButton = document.getElementById('cancel');
const cancelSignal = new Promise((resolve) => {
cancelButton.addEventListener('click', () => resolve());
});
cancellableOperation(longOperation, cancelSignal)
.then(result => console.log('Operation completed:', result))
.catch(error => {
if (error.message === 'Operation cancelled') {
console.log('Operation was cancelled by user');
}
});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]);
}
// Usage
fetchWithTimeout('/api/data', 3000)
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.json();
})
.then(data => console.log('Data:', data))
.catch(error => {
if (error.message.includes('timeout')) {
console.error('Request took too long');
} else {
console.error('Error:', error.message);
}
});function fetchWithCacheFallback(url) {
const cachePromise = getCachedData(url);
const networkPromise = fetch(url).then(response => response.json());
return Promise.race([cachePromise, networkPromise])
.catch(() => {
// If first promise fails, wait for second
return Promise.race([networkPromise, cachePromise]);
});
}
// Usage
fetchWithCacheFallback('/api/user-profile')
.then(data => {
console.log('Data received (fastest):', data);
return data;
})
.catch(error => console.error('All sources unavailable:', error));function fetchFromFastestAPI(endpoint) {
const apis = [
fetch(`https://api1.example.com${endpoint}`),
fetch(`https://api2.example.com${endpoint}`),
fetch(`https://api3.example.com${endpoint}`)
];
return Promise.race(apis)
.then(response => {
if (!response.ok) throw new Error('API error');
return response.json();
});
}
// Usage
fetchFromFastestAPI('/users/123')
.then(userData => {
console.log('User data from fastest API:', userData);
})
.catch(error => console.error('All APIs unavailable:', error));If the first completed promise rejects, Promise.race() also rejects:
const successPromise = new Promise(resolve => setTimeout(() => resolve('success'), 2000));
const errorPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('error')), 1000));
Promise.race([successPromise, errorPromise])
.then(result => console.log('Result:', result))
.catch(error => console.error('Error:', error.message)); // 'error'If passed an empty array, Promise.race() hangs in pending state:
// ❌ Will never resolve
Promise.race([])
.then(result => console.log(result));
// ✅ Check for empty array
function safeRace(promises) {
if (promises.length === 0) {
return Promise.resolve();
}
return Promise.race(promises);
}Promise.race() converts non-promise values to resolved promises:
Promise.race([42, Promise.resolve('string'), new Promise(resolve => setTimeout(() => resolve(true), 1000))])
.then(result => console.log(result)); // 42 (number instantly converted to resolved promise)// ❌ May miss errors
Promise.race([fetch('/api/data1'), fetch('/api/data2')])
.then(data => console.log(data));
// ✅ Proper handling
Promise.race([fetch('/api/data1'), fetch('/api/data2')])
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Race error:', error));// ❌ Remaining promises continue executing
const promises = [slowOperation(), fastOperation()];
Promise.race(promises)
.then(result => {
console.log('Fast result:', result);
// slowOperation() still runs in background
});
// ✅ Cancel remaining operations (using AbortController)
const controller = new AbortController();
const signal = controller.signal;
const promises = [
fetch('/api/slow', { signal }),
fetch('/api/fast', { signal })
];
Promise.race(promises)
.then(response => {
controller.abort(); // Cancel other requests
return response.json();
})
.then(data => console.log(data));Promise.race() is a powerful tool for optimizing asynchronous operations, allowing you to select the first available result from multiple parallel operations, which is especially useful for implementing timeouts and getting data from the fastest sources.
Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪