5

I would like to know, if awaiting a resolved promise, will lead to a synchronous code execution or may lead to asynchronous one.

I made this little snippet to check on browsers :

const promise = new Promise((resolve) => {
  console.log('exec promise');
  setTimeout(() => {
    console.log('executed promise');
    resolve();
  }, 1000);
});

(async () => {
  console.log('start');
  for (let i = 0; i < 1e8; i += 1) {
    await promise;
  }
  console.log('end');
})();

And it looks like browsers make it synchronous (considering the screen freeze).

BUT ... is it due to browser specific implementation ? Or by design ?

Poyoman
  • 1,652
  • 1
  • 20
  • 28
  • 1
    Using `await` inside a classic for loop works synchronously, yes. –  Oct 15 '20 at 08:59
  • 1
    Sequential, not synchronous. The IIFE still returns (a promise) immediately, and other code runs independently. – Bergi Oct 15 '20 at 12:12

2 Answers2

5

Here's an alternative demo that doesn't freeze the browser (which I think is getting the way of what we're trying to show), and which shows the opposite behaviour to your initial conclusion:

const p = new Promise((r) => r());

p.then(() => {

  (async() => {
    console.log('a');
    await p;
    console.log('p');
  })()
  console.log('b');

});

In this case, we get hold of p, a resolved promise. We then kick off an async function that awaits it. The output is:

a
b
p

IE the async function still returned control back (and b was logged) when it hit the await on the already resolved p. Only after b was logged and the event loop was free to return execution back to after the await was p subsequently logged.

Is this standard behaviour? Yes.

The specification effectively turns the awaited expression to the then part of a promise, and in steps 9 and 10 of a subsequent step makes the decision on how to proceed.

  1. If promise.[[PromiseState]] is pending, then
    a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]].
    b. Append rejectReaction as the last element of the List that is promise.[PromiseRejectReactions]].
  2. Else if promise.[[PromiseState]] is fulfilled,
    a. then Let value be promise.[[PromiseResult]].
    b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value)
    c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]).

Step 9 says if it's pending, to add the generated then to the list of things to be called when the promise is fulfilled.

Step 10 gets to the heart of your question - it says if it's fulfilled already, to enqueue the job - ie to place it on the queue of things to be called back.

The spec effectively says that an await should never return synchronously.

James Thorpe
  • 31,411
  • 5
  • 72
  • 93
2

This is expected behavior. You freeze the event loop there, because the promise has already been resolved, the next await operator will wait&resolve for the next promise descriptor on the same iteration of the event loop. But this behavior can be platform-specific, depending on which promise implementation you're actually using - native or shim.

const promise= Promise.resolve();

(async()=>{
    for(let i=0; i< 10000000; i++) {
        await promise;
    }
    console.log('end');
})()

setTimeout(()=> console.log('nextEventLoopTick'), 0);

Will output the follwing:

end
nextEventLoopTick

As you can see, the async fn will be fully resolved before setTimeout. So, this indicates that the async function is resolved on the same eventloop tick.

Dmitriy Mozgovoy
  • 1,419
  • 2
  • 8
  • 7