🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
JS MASTER CLASS /// MASTER THE ENGINE /// BUILD LOGIC /// ASYNC PATTERNS /// JS MASTER CLASS /// MASTER THE ENGINE ///

Promise.race() Deep Dive | JavaScript Tutorial - In-Depth Guide

Go deeper on Promise.race(): its settle-on-first behavior (success or failure), the manual timeout pattern it enables, and why losing promises are not cancelled.

Total XP: 0|💻 javascript XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

If the fastest promise passed to Promise.race() rejects, does race() reject too?


🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Promise.race() settles as soon as the first input promise settles — whether that's a success or a failure. This makes it the standard building block for implementing request timeouts.

1Promise.race() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 1

Promise.race() settles the instant the first input promise settles, adopting that same outcome — fulfilling if the first one fulfills, or rejecting if the first one rejects.

+
const first = await Promise.race([
  delay(100).then(() => 'fast'),
  delay(50).then(() => Promise.reject('quick failure')),
]);
// rejects with 'quick failure', since it settled first
localhost:3000
🏁

First to Settle, Win or Lose

2Promise.race() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 2

The most common real-world use of Promise.race() is implementing a timeout: race a real operation against a promise that rejects after a fixed delay.

+
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([promise, timeout]);
}
localhost:3000

The Timeout Pattern

3Promise.race() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 3

The losing promise in a race() is not cancelled — it keeps running and its eventual result (or side effects) are simply ignored.

+
// If the timeout wins, the actual fetch() keeps running:
await withTimeout(fetch('/api/slow'), 3000); // throws 'Timeout'
// but the underlying fetch() request is still in flight
localhost:3000

Losers Aren't Cancelled

4Promise.race() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 4

Combining Promise.race() with AbortController lets you both enforce a timeout AND actually stop the underlying operation when it loses the race.

+
function fetchWithTimeout(url, ms) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), ms);
  return fetch(url, { signal: controller.signal }).finally(() => clearTimeout(timeout));
}
localhost:3000

Racing + Actually Cancelling

5Promise.race() Deep Dive | JavaScript Tutorial - In-Depth Guide Part 5

Promise.race() on an empty array never settles — there is no 'first' promise to determine an outcome, so the returned promise stays pending forever.

+
Promise.race([]); // pending forever, never settles
localhost:3000

Empty Array Never Settles

6Step-by-Step Breakdown

Promise.race() settles the instant the first input promise settles, adopting that same outcome — fulfilling if the first one fulfills, or rejecting if the first one rejects.

Checkpoint: If the fastest promise passed to Promise.race() rejects, does race() reject too?

  • Yes, race() adopts whatever outcome settles first
  • No, race() only rejects if every promise rejects

The most common real-world use of Promise.race() is implementing a timeout: race a real operation against a promise that rejects after a fixed delay.

The losing promise in a race() is not cancelled — it keeps running and its eventual result (or side effects) are simply ignored.

Checkpoint: If a real fetch() request loses a Promise.race() against a timeout, does the actual network request stop?

  • Yes, race() automatically cancels the loser
  • No, it keeps running unless separately aborted

Combining Promise.race() with AbortController lets you both enforce a timeout AND actually stop the underlying operation when it loses the race.

Promise.race() on an empty array never settles — there is no 'first' promise to determine an outcome, so the returned promise stays pending forever.

Next, we'll explore 'Promise.any() Deep Dive'.

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Show a Clear Timeout Message, Not a Silent Failure, When race()'s Timeout Wins

When a race()-based timeout triggers, ensure the resulting error state is announced accessibly (e.g. via an ARIA live region: 'Request timed out, please try again') rather than leaving the UI in an ambiguous loading or blank state.

SEO Implications

  • 1

    Timeouts Prevent Hung Requests from Blocking Server-Side Rendering

    Using a race()-based timeout around external data fetches during server-side rendering prevents a single slow upstream API from stalling page generation indefinitely, protecting Time to First Byte.

Best Practices

Always Pair a Timeout race() with an AbortController for Real Cancellation

A timeout that only stops your code from waiting, without stopping the underlying request, wastes network/server resources on work whose result will be discarded.

Clear the Timeout Once the Real Operation Wins

Failing to clearTimeout() after the real promise resolves first leaves a dangling timer that will fire uselessly later — always clean it up in a .finally() block.

Frequent Bugs

THE BUG

Implementing a timeout with race() but forgetting to also cancel the underlying operation, so a 'timed out' request keeps consuming bandwidth and server resources in the background.

THE FIX

Wire the timeout to call an AbortController's abort() method, and pass its signal into the real operation (like fetch), so losing the race also actually stops the work.

THE BUG

Leaving a timeout's setTimeout uncleared after the real promise wins the race, causing a stray timer to fire later for no reason.

THE FIX

Use `.finally(() => clearTimeout(timeoutId))` on the raced promise to guarantee cleanup regardless of which side wins.

Real-World Examples

Enforcing a Maximum Wait Time on a Slow Third-Party API

An app integrated with a third-party API that occasionally hung indefinitely, and needed to guarantee a response (or a clear timeout error) within 5 seconds.

async function fetchWithTimeout(url, ms = 5000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), ms);
  try {
    return await fetch(url, { signal: controller.signal });
  } finally {
    clearTimeout(timer);
  }
}

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Timeout stops your code from waiting but not the actual request

controller.abort(); // actually stops the fetch, not just the wait

The Solution //

Combine race() with AbortController so the timeout also cancels the underlying operation.

Lesson Glossary

[01]Promise.race()

Settles with the outcome of whichever input promise settles first, success or failure.

Code Preview
Promise.race([p1,p2])

[02]Timeout Pattern

Racing an operation against a delayed-rejection promise to enforce a maximum wait time.

Code Preview
race([op, timeout])

[03]AbortController

An API for actually cancelling an in-flight operation like fetch(), often combined with race() for true cancellation.

Code Preview
new AbortController()

[04]Losing Promise

The promise(s) in a race() that settle after the winner; they aren't cancelled by race() itself.

Code Preview
still runs

[05]Never-Settling Promise

A promise that neither resolves nor rejects, as with Promise.race([]).

Code Preview
race([])

Continue Learning