How JavaScript Promises and Promise.race Resolve: A Deep Dive into Asynchronous Settlement

JavaScript Promises settle asynchronously via the micro-task queue, while Promise.race returns a new promise that fulfills or rejects immediately when the first promise in the iterable settles, regardless of the outcome.

Understanding how JavaScript Promises and Promise.race resolve is essential for mastering asynchronous programming in modern JavaScript. The lydiahallie/javascript-questions repository provides authoritative explanations of these behaviors in its comprehensive README.md (lines 1380-1392), detailing exactly how promise settlement and racing mechanics work under the hood.

How JavaScript Promises Resolve Internally

The Synchronous Executor and Asynchronous Settlement

When you create a promise with new Promise((resolve, reject) => { … }), the executor function runs immediately and synchronously. However, calls to resolve(value) or reject(reason) do not execute handlers instantly. Instead, they queue the settlement as a micro-task, ensuring that .then() and .catch() handlers execute only after the current call stack clears.

The Micro-task Queue and Handler Execution

Once a promise settles, all attached handlers are placed in the micro-task queue. These handlers execute before any macro-tasks (such as setTimeout callbacks) in the next event loop iteration. This guarantees that promise chains resolve predictably, even when nested within synchronous code blocks.

How Promise.race Resolves the First Settled Promise

The Promise.race(iterable) static method returns a new promise that adopts the state of the first promise to settle in the provided iterable. According to the lydiahallie/javascript-questions source, this method internally attaches then and catch handlers to each promise; the first invocation of either handler settles the race promise with that value or reason.

Settlement Behavior on Resolve vs Reject

Promise.race treats fulfillment and rejection equally. If the first promise to settle resolves, the race promise resolves with that value. If the first promise rejects, the race promise rejects with that reason. This behavior makes Promise.race suitable for implementing timeouts, where a rejection might be the desired "winning" outcome.

Practical Implementation from the Source

The repository demonstrates this with two competing timers:

const firstPromise = new Promise((res) => setTimeout(res, 500, 'one'));
const secondPromise = new Promise((res) => setTimeout(res, 100, 'two'));

Promise.race([firstPromise, secondPromise]).then(res => console.log(res));
// → logs "two"

Here, secondPromise resolves after 100ms while firstPromise resolves after 500ms. The race promise fulfills with 'two' because that promise settled first, as documented in README.md lines 1380-1392.

Code Examples

Example 1 – Basic promise resolution

// A simple promise that resolves after 200ms
const p = new Promise((resolve) => setTimeout(() => resolve(42), 200));

p.then(value => console.log('resolved with', value));
// Output (after ~200ms): resolved with 42

Example 2 – Promise.race with mixed resolve/reject

const fastReject = new Promise((_, reject) =>
  setTimeout(() => reject(new Error('boom')), 50)
);
const slowResolve = new Promise((resolve) =>
  setTimeout(() => resolve('ok'), 150)
);

Promise.race([fastReject, slowResolve])
  .then(v => console.log('won:', v))
  .catch(err => console.error('lost:', err.message));
// Output (after ~50ms): lost: boom

Example 3 – Using Promise.race to implement a timeout

function fetchWithTimeout(url, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('timeout')), ms)
  );
  const request = fetch(url);
  return Promise.race([request, timeout]);
}

fetchWithTimeout('https://example.com', 3000)
  .then(r => console.log('got response'))
  .catch(e => console.error(e.message));

Summary

  • JavaScript promises settle asynchronously via the micro-task queue, ensuring handlers execute after the current synchronous code completes.
  • The executor function runs synchronously, but resolve and reject queue settlement as micro-tasks.
  • Promise.race returns a promise that adopts the state of the first settled promise in the iterable, whether it fulfills or rejects.
  • This racing behavior is implemented by attaching handlers to each promise and settling immediately upon the first invocation, as shown in lydiahallie/javascript-questions/README.md (lines 1380-1392).

Frequently Asked Questions

Does Promise.race wait for all promises to complete?

No. Promise.race settles as soon as the first promise in the iterable settles, regardless of whether other promises are still pending. The remaining promises continue executing in the background, but their eventual results are ignored by the race promise.

What happens if the first promise in a race rejects?

If the first promise to settle rejects, Promise.race returns a rejected promise with that reason. Unlike Promise.any, which waits for a fulfillment, Promise.race treats rejection as a valid settlement condition and immediately propagates the error to the returned promise.

How does Promise.race differ from Promise.any?

Promise.race settles with the first promise to fulfill or reject, while Promise.any settles only when the first promise fulfills. If all promises passed to Promise.any reject, it returns an AggregateError. In contrast, Promise.race would have already rejected as soon as the first rejection occurred.

Can Promise.race be used for request timeouts?

Yes. This is a common pattern where one promise represents the async operation (e.g., a fetch request) and another represents a timer that rejects after a specified duration. Promise.race between these two promises ensures that if the timer expires first, the race rejects with a timeout error, effectively aborting the wait for the slow request.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →