0

The custom implementation below of Promise.race is working correctly when I pass a Promise object to it. However, if I pass Promise.reject it sort of dismisses it and just resolves to the Promise.resolve value.

function race(promises) {
  return new Promise((resolve, reject) => {
    function resolveCB(value) {
      resolve(value);
    }

    function rejectCB(value) {
      reject(value);
    }

    promises.forEach((p) => {
      p.then(resolveCB).catch(rejectCB);
    });
  });
}

const p1 = Promise.reject(1)
const p2 = Promise.reject(5)
const p3 = Promise.resolve(2)

race([p1, p2, p3]).then(result => console.log(result)).catch(err => console.log(err));

The above code logs 2. I would expect it to log 1.

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => reject(1), 1500);
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve(6), 1000);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => reject(8), 500);
});

This logs 8 as expected, so the issue is not in the function race it seems but in the misunderstanding of Promise.reject I guess?

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
SrdjaNo1
  • 755
  • 3
  • 8
  • 18
  • Have a look at the [difference between `.then(…, …)` and `.then(…).catch(…)`](https://stackoverflow.com/q/24662289/1048572) – Bergi Oct 10 '22 at 19:37
  • I should mention that passing three promises to your `race()` function, all of which are already resolved or rejected when you pass them is not particularly a real world use case since NONE really resolved or rejected first, they were ALL already resolved or rejected. So, probably you'd just have to follow what the spec says for that unusual case. In most real world uses, these promises will resolve/reject sometime in the future and one will legitimately come first. – jfriend00 Oct 10 '22 at 21:17

1 Answers1

0

If you switch the order of then and catch in your forEach, you can see the results you're expecting. The then takes up the space in the microtask queue even though the Promise rejection and lack of a catch handler means it doesn't actually transform the promise.

promises.forEach((p) => {
  // This demonstrates how the results are sensitive to the order, but
  // creates the opposite ordering: rejected promises are favored
  // over resolved ones.
  p.catch(rejectCB).then(resolveCB);
});

function race(promises) {
  return new Promise((resolve, reject) => {
    function resolveCB(value) {
      resolve(value);
    }

    function rejectCB(value) {
      reject(value);
    }

    promises.forEach((p) => {
      p.catch(rejectCB).then(resolveCB);
    });
  });
}

const p1 = Promise.reject(1)
const p2 = Promise.reject(5)
const p3 = Promise.resolve(2)

race([p1, p2, p3]).then(result => console.log(result)).catch(err => console.log(err));

It'd be better, though, just to use the normal two-arg behavior of then so both of those outcomes happen at the same time:

promises.forEach((p) => {
  p.then(resolveCB, rejectCB);
});

function race(promises) {
  return new Promise((resolve, reject) => {
    function resolveCB(value) {
      resolve(value);
    }

    function rejectCB(value) {
      reject(value);
    }

    promises.forEach((p) => {
      p.then(resolveCB, rejectCB);
    });
  });
}

const p1 = Promise.reject(1)
const p2 = Promise.reject(5)
const p3 = Promise.resolve(2)

race([p1, p2, p3]).then(result => console.log(result)).catch(err => console.log(err));

(Outside of a toy or homework problem, you should be using the built-in race or a common polyfill. FWIW, the core-js polyfill uses the two-arg then.)

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • "*If you switch the order, you get the right results.*" - well then you always get the rejected value, even if there is a fulfilled one that should come first. – Bergi Oct 10 '22 at 19:43
  • @Bergi Correct! The top snippet *demonstrates* the change in order causes the change in behavior, and the bottom snippet demonstrates the endorsed more-reliable solution. If it wasn't clear to you that the top one isn't bulletproof, I can make it more obvious. – Jeff Bowman Oct 10 '22 at 20:03
  • FYI, code attempting to emulate `Promise.race()` should not use `.forEach()` because `Promise.race()` takes any iterable, not just an array. It should use `for/of` to iterate the iterable. – jfriend00 Oct 10 '22 at 21:18
  • @jfriend00 I agree with you, but I tried to keep my feedback specific to the OP's question (and understanding of Promise handling) rather than turning it into a more general Code Review post. – Jeff Bowman Oct 10 '22 at 21:36