3

Experiencing what appears to be weird promise resolution behavior in JS/TS. Node LTS. It's completely possible that it comes down to whether the promise resolves to a value that is then read later on in the code, vs fire-and-forget (void response type). Hoping for experts to weigh in.

According to this Google dev blog post, you should be able to cut wait time in half by deferring your await call on two async/await functions.

Original post: https://web.dev/async-functions/#careful-avoid-going-too-sequential

However.. on node.js (both v16 and v18/lts) that logic doesn't work.

If we encapsulate their example in an async IIFE, we can demo it with this code:

(async () => {
  function wait(ms, value) {
    return new Promise(resolve => setTimeout(resolve, ms, value));
  }

  const t = 500
  const msg = 'done'
  const wait1 = wait(t, msg); // Start a 500ms timer asynchronously…
  const wait2 = wait(t, msg); // …meaning this timer happens in parallel.
  await wait1; // Wait 500ms for the first timer…
  await wait2; // …by which time this timer has already finished.
  console.log(wait1, wait2)
})()

Notice the values of wait1 and wait2 in the console.log ending statement.

console.log(wait1,wait2)
// Promise { 'done' } Promise { 'done' }

Why are wait1 and wait2 still unresolved promises if we called await on them?

Consider this additional logic, which doesn't make me feel better about the logic flow happening here. If we call await on those variables again in console.log, the promise is resolved...

console.log(await wait1, await wait2)
// done done

So by calling await on these variables again we finally get resolved promise values?

Is this a weird difference between Node and Chrome's implementation of V8, or is this the intended behavior when we're returning a resolved promise's value vs when we are only awaiting a function's execution with a void response?

  • 3
    They're not unresolved promises, that's what "done" means. – Pointy Dec 07 '22 at 18:46
  • 1
    Where exactly do you think there is a difference? Your code gives the same output on both Node and Chrome as far as I can see. `wait1` and `wait2` are promises (and stay being promises when resolved), and `await wait1`/`await wait2` are the values of thoses promises. – Guillaume Brunerie Dec 07 '22 at 18:55
  • 1
    The Promise object doesn't magically become its resolved value. It's still a Promise object, even after it's been resolved. – Kaiido Dec 07 '22 at 23:22
  • "*you should be able to cut wait time in half by deferring your await call on two async/await functions.*" - this [is a really bad idea](https://stackoverflow.com/questions/46889290/waiting-for-more-than-one-concurrent-await-operation) and [should absolutely be avoided](https://stackoverflow.com/questions/45285129/any-difference-between-await-promise-all-and-multiple-await). – Bergi Dec 08 '22 at 02:29
  • @GuillaumeBrunerie as shown in the original question, the output of console.log on those is not the same. The first time you call await on `wait1` and then console.log it, the output shows `Promise { 'done' }`. If you update your console.log call to use `await` a second time on the same variable, only then is it unwrapped. – staysense_dev_1 Jan 26 '23 at 16:06
  • @Bergi agreed that using Promise.all or allSettled is better. I'm more asking clarifying questions from Google's claims in that article since I'm getting results that seem bizarre or undesirable. – staysense_dev_1 Jan 26 '23 at 16:07
  • @staysense_dev_1 Logging a promise and a value is obviously different, but both node and Chrome behave the exact same way. – Guillaume Brunerie Jan 26 '23 at 16:24

1 Answers1

1

Why are wait1 and wait2 still unresolved promises if we called await on them?

Promise objects don't stop being promise objects when they are fulfilled with a result. There's no magic going on, and the variables you have still hold the promises themselves no the results. An await expression does return the result value, but the statement await wait1; simply discards that result.

You would want to write

const wait1 = wait(500, 'done1');
const wait2 = wait(400, 'done2');
const res1 = await wait1;
const res2 = await wait2;
console.log(res1, res2); // instead of console.log(wait1, wait2)
//          ^^^^  ^^^^

or rather (much better)

const [res1, res2] = await Promise.all([wait1, wait2]);
console.log(res1, res2);
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • So this makes perfect sense, and is definitely the pattern I've followed in my own code, but this Google article was really confusing when I was thinking about the practicality of what they were demonstrating. This is helpful. Thank you! – staysense_dev_1 Jan 26 '23 at 16:09