Does awaiting a non-Promise have any detectable effect?
Asked Answered
M

3

108

One can await a non-Promise and that's good so.

All these expressions are valid and cause no error:

await 5
await 'A'
await {}
await null
await undefined 

Is there any detectable effect of awaiting a non-Promise? Is there any difference in behavior one should be aware of to avoid a potential error? Any performance differences?

Are the following two lines completely same or do they theoretically differ?:

var x = 5
var x = await 5

How? Any example to demonstrate the difference?

PS: According TypeScript authors, there is a difference:

var x = await 5; is not the same as var x = 5;; var x = await 5; will assign x 5 in the next tern, where as var x = 5; will evaluate immediately.

Mafia answered 20/3, 2019 at 14:19 Comment(0)
I
124

await is not a no-op. If the awaited thing is not a promise, it is wrapped in a promise and that promise is awaited. Therefore await changes the execution order (but you should not rely on it nevertheless):

The following outputs 1, 2, 3:

console.log(1);

(async function() {
  var x = await 5;
  console.log(3);
})();

console.log(2);

With the await removed it's 1, 3, 2:

    console.log(1);

    (async function() {
      console.log(3);
    })();

    console.log(2);

Additionally await does not only work on instanceof Promises but on every object with a .then method:

await { then(cb) { /* nowhere */ } };
console.log("will never happen");

Is there any detectable effect of awaiting a non-Promise?

Sure, .then gets called if it exists on the awaited thing.

Is there any difference in behavior one should be aware of to avoid a potential error?

Don't name a method "then" if you don't want it to be a Promise.

Any performance differences?

Sure, if you await things you will always defer the continuation to a microtask. But as always: You won't probably notice it (as a human observing the outcome).

Impostume answered 20/3, 2019 at 14:23 Comment(5)
"but you should not rely on it nevertheless" Why not?Reliquary
@aBetterOliver example: you start two very short running actions and want to work with their result. You know that action 1 finsihes earlier than action 2, so you store the result of 1 in a variable, and access that when 2 is done. Now someone somewhen changes the algorithm, so that 1 is slower than 2. Your code breaks and no one knows why. If you have to execute async tasks in a specific order, you should write that down explicitly.Impostume
@JonasWilms Your example does not relate to your statement. You stated that one should not rely on the fact that await changes the execution order. This is wrong, we rely on it all the time (i.e. when exploiting the atomicity of synchronous blocks). Your example is about two async tasks and one relying on the empirical (yet not guaranteed) execution time of the other, which is just a general observation of parallel programming: You cannot rely on the order of incomparable transactions in the precedence graphAuvil
I disagree that the first example parallels (pardon the pun) the OP's example. To do so you'd have to await the console log, await console.log(3);, in which case there is no observable difference in behavior.Macy
There is a case in which it is useful to rely on this behavior: Imagine you have a function that takes a callback that returns T | PromiseLike<T>, inside your function you don't know if this callback is async or not, so instead of doing if (result instanceof Promise) await result or something similar, you can just do await callback(), it is cleanerTerritorial
G
34

Completely agreed with Jonas's statements. One thing that was not answered in his question was Are the following two lines completely same or do they theoretically differ?:

following two lines are not completely same, they're theoretically different.

  1. var x = 5
  2. var x = await 5

execution time in my console for 1st and 2nd statement is 0.008056640625ms and 0.055908203125ms respectively. async/await, setTimeOut etc are APIs provided by Run time in which JavaScript Run time is running. Putting await on a non-promise will be executed in event-loop. Line 1 will be executed right after reaching the stack but the line 2 will take few time(milliseconds) as it will 1st go to the stack and then to the task queue after skipping webAPI waiting section because there's no promise to be resolved & finally after that control will be given to stack again for execution.

Gibberish answered 21/3, 2019 at 8:49 Comment(6)
I think this question was already answered with the example code snippet. Thank you anyway!Mafia
actually the last part of the question was missing though i.e Are the following two lines completely same or do they theoretically differ?.Gibberish
The example code from @JonasWilms showed the difference very clearly...Mafia
great. assume it an other explanation then :)Gibberish
This is an important distinction, because this behavior was "fixing" a bug for us implemented by a junior engineer that didn't realize the function they were awaiting wasn't a promise and thus shouldn't be doing anything. "But it fixes the bug!" (a race condition bug that was "solved" by putting the non-promisified function onto the event loop... after other events) they had said. Little did we know none of us knew about this behavior of await....Subacid
But will it be synchronous. Can I be guaranteed that, in your example, a line 3. will never get executed before line 2. is complete?Egalitarian
M
0

The awaited code gets wrapped in a promise if the thing awaited does not have a then property. Therefore, though there is a difference in performance (it takes longer), there is no observable difference in behavior unless what you await has a then property -- in which case behavior might be unpredictable if the thing awaited is not actually a promise which will complete.

Macy answered 17/1, 2023 at 21:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.