1

TL;DR: Will already resolved promises always beat setImmediate in a race?

Background:

Sometimes you want to know if a promise is resolved without awaiting its completion. There are some old legacy tricks like using util.inspect to get the internal state and checking if it contains the string "<pending>". However, a more stable solution is to use Promise.race() to essentially wait for your unknown promise with a (very small) timeout.

const p = doSomething();

const result = await Promise.race([
    p, 
    new Promise(resolve => setTimeout(resolve, 1))
]);

if (result) {
   // p was resolved within 1 ms!
   ...
}

This will at most wait 1 ms to get result, which will then either contain the resolved value of p or undefined. If required, the "timeout promise" may of course return something different than undefined to distinguish actual undefined values returned from doSomething():

const PENDING = Symbol.for('PENDING');
const result = await Promise.race([
    p, 
    new Promise(resolve => setTimeout(() => resolve(PENDING), 1))
]);

if (result !== PENDING) {
  ...
}

Now we'll either get the resolved value from doSomething() or the unique symbol PENDING.

Now to my question. In addition to setTimeout, there's a setImmediate function that basically behaves like a timer that expires immediately; it just gives the event loop a go before resolving. When using this in my Promise.race expression above, empirically it seems to work, i.e., if p is already resolved it will beat setImmediate in a race, but I'm wondering if there is any such guarantee - or if I should be using a timer of 1 ms to guarantee that a resolved promise beats the timer?

I've tried putting p before and after the setImmediate promise in the array passed to Promise.race and it worked both ways, but still I'm worried it might behave randomly or be OS dependent? Or is the fact that setImmediate waits for one round of I/O enough to guarantee that any resolved promises will win?

From the documentation:

Schedules the "immediate" execution of the callback after I/O events' callbacks

Edit:

I found that even this "seems" to work:

const result = await Promise.race([p, new Promise(resolve => resolve(PENDING))]);

or actually even this:

const result = await Promise.race([p, Promise.resolve(PENDING)]);

However, here the order is important. If p is resolved and is before the timeout promise, it will win, but if it's after the timeout promise in the array it will lose. The question is the same though: is this approach guaranteed to let p win if it's already resolved?

JHH
  • 8,567
  • 8
  • 47
  • 91
  • Does this answer help you? https://stackoverflow.com/a/68678528/14032355 – ikhvjs Oct 06 '21 at 08:57
  • Thanks, but I'm not sure if it provides any evidence about how Promise.race() is guaranteed to behave. – JHH Oct 06 '21 at 09:01
  • The documentation say it. The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race – ikhvjs Oct 06 '21 at 09:06
  • Sure, but "as soon as one" is vague when the question is what happens if both are resolved. – JHH Oct 06 '21 at 10:51
  • The answer I mentioned in the comment above show you the sequence of the function executed by the event loop in nodejs and it explains which promise would be resolved first if they both executed at the same time. – ikhvjs Oct 06 '21 at 11:11

1 Answers1

1

Considering the codes below in Nodejs

Ref. https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

setImmediate(() => console.log("setImmediate"));
setTimeout(() => console.log("setTimeout"));
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("Promise"));
console.log("sync");

Output:

You can notice the sequence and that's how it is executed in order.

sync
nextTick
Promise
setTimeout
setImmediate

To answer your question, we can wrap the codes in Promises like below:

(async function main() {
  const result = await Promise.race([
    new Promise(resolve => resolve("sync")),
    new Promise(resolve => setImmediate(() => resolve("setImmediate"))),
    new Promise(resolve => setTimeout(() => resolve("setTimeout"))),
    new Promise(resolve => Promise.resolve().then(() => resolve("Promise"))),
    new Promise(resolve => process.nextTick(() => resolve("nextTick"))),
  ]);
  console.log({ result });
})();

As sync function is to be executed first, so it would be returned.

Output:

{ result: 'sync' }

You can comment one of the Promise above to see which one resolve first.

ikhvjs
  • 5,316
  • 2
  • 13
  • 36