What is Promise.race() for?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Medium
#JavaScript #Asynchronicity #JS Basics

Brief Answer

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:

  • Timeouts — limiting execution time of asynchronous operations
  • Data races — getting data from the fastest source
  • Operation cancellation — stopping execution on first result

Main Idea:

“Winner takes all!” 🏁

🏎️ How It Works:

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));

Full Answer

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.

How Promise.race() Works

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)

Main Use Cases

1. Implementing Timeouts

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);
    }
  });

2. Getting Data from Fastest Source

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));

3. Canceling Long Operations

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');
    }
  });

Practical Examples

Timeout for Fetch Request

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);
    }
  });

Race Between Cache and Network

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));

Competition Between Multiple APIs

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));

Features and Behavior

1. Error Handling

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'

2. Empty Array

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);
}

3. Non-promise Values

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)

Common Mistakes and Solutions

1. Improper Handling of All Possible Outcomes

// ❌ 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));

2. Ignoring Remaining Promises

// ❌ 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));

Best Practices

  1. Always handle errors — use .catch() to handle rejections
  2. Use for timeouts — effective way to limit execution time
  3. Apply for backup sources — get data from fastest source
  4. Cancel unnecessary operations — save resources on first result
  5. Check empty arrays — avoid hanging Promise.race()

Key Promise.race() Benefits

  1. Performance — instant reaction to first result
  2. Flexibility — supports various race scenarios
  3. Ease of use — intuitive API
  4. Compatibility — works with all promise types
  5. Resource efficiency — ability to cancel remaining operations

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 💪