Skip to content

Instantly share code, notes, and snippets.

@unscriptable
Forked from briancavalier/ambiguous-race.js
Last active September 25, 2019 13:20
Show Gist options
  • Save unscriptable/06f8984d6cb0fd6be1c0 to your computer and use it in GitHub Desktop.
Save unscriptable/06f8984d6cb0fd6be1c0 to your computer and use it in GitHub Desktop.
Promise.race is a lie
// This is the function we will use to call Promise.race().
// logWinner is simply a function that races two promises
// and logs the "winner" of the race as a side effect.
function logWinner (p1, p2) {
Promise.race([p1, p2]).then(console.log.bind(console));
}
// Here are 2 promises, p1 and p2. p2 always resolves
// first, since p1 resolves in 20 ms, and p2 resolves
// in 10 ms. By any reasonable definition of "race",
// p2 should be the winner.
var p1 = new Promise(function(resolve) {
setTimeout(function() { resolve('p1'); }, 20);
});
var p2 = new Promise(function(resolve) {
setTimeout(function() { resolve('p2'); }, 10);
});
// Given the same inputs, you would expect that Promise.race
// would behave the same *no matter when you call it*. However,
// Promise.race behaves differently depending on *when* it is
// called. In an async environment (e.g. browsers, node),
// you can never determine when something will be called in
// all but the most trivial cases.
// If Promise.race is called *before* any of the raced promises
// are resolved, logWinner logs the correct winner, "p2".
setTimeout(function() { logWinner(p1, p2); }, 0);
// If Promise.race is called *after* any of the raced promises
// are resolved, logWinner logs the lowest resolved promise
// in the array, "p1".
setTimeout(function() { logWinner(p1, p2); }, 50);
// Therefore, Promise.race is non-deterministic.
@joeyhub
Copy link

joeyhub commented Sep 25, 2019

Please see these:

You case looks like a new variation of the first I've not seen before though it's not entirely unexpected.
By the time you call Promise.race, both promises are resolved. At this point Promise.race doesn't have the information to order them.

The work around that would be to check if resolved immediately after hooking it to the event in the correct order up front though I don't know of an explicit mechanism for that (it's an encapsulate state).

Basically do Promise.race() for each added timer? It's annoying as it needs to be order aware that things should finish in that way but not really much you can do about it. Technically speaking if the issue I found is fixed then it could be possible to put an autoincrement order id on the promises. Promise.race only works properly when all or all but one promises are in a pending state.

It's turning up to the finishing line and seeing both are there already. It doesn't have an action replay.

Thanks for the assist, I missed that in my POC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment