Chong Lip Phang
Chong Lip Phang

Reputation: 9279

To what extent are 'asynchronous functions' asynchronous in ECMAScript-2017?

ECMAScript-2017, just finalized about a month ago, introduces 'asynchronous functions' as a new feature. To find out how 'asynchronous' they are, I carried out a test in Chrome:

async function af1(){
   for (let i=0; i<300; i++)
      await (new Promise(
                (resolve,reject)=>{
                   for (let j=0; j<=4; j++) console.log(j);resolve();}))
            .then(()=>{for (let j=100; j<=400; j+=100) console.log(j);});;
}

async function af2(){
   for (let i=0; i<300; i++)
      await (new Promise(
                (resolve,reject)=>{
                   for (let j=5; j<=9; j++) console.log(j);resolve();}))
            .then(()=>{for (let j=500; j<=900; j+=100)  console.log(j);});
}
af1();
console.log(300);
af2();
console.log(400);
// 0 1 2 3 4 300 5 6 7 8 9 400 100 200 300 400 500 600 700 800 900
// 0 1 2 3 4 5 6 7 8 9 100 200 300 400 500 600 700 800 900
// 0 1 2 3 4 5 6 7 8 9 100 200 300 400 500 600 700 800 900
// 0 1 2 3 4 5 6 7 8 9 100 200 300 400 500 600 700 800 900
// 0 1 2 3 4 5 6 7 8 9 100 200 300 400 500 600 700 800 900
// 0 1 2 3 4 5 6 7 8 9 100 200 300 400 500 600 700 800 900
// ......

Actually I was expecting a more randomized sequence.

Now, can I safely say that each block of code representing a promise or one of its then() callbacks is atomic, in the sense that execution of the code within the block is not interruptible by another part of the code within the same program?

Upvotes: 2

Views: 76

Answers (1)

trincot
trincot

Reputation: 350345

Writing asynchronous code does not imply that the output will be random. When promises are resolved immediately (instead of being dependent on some uncontrolled external event), then the sequence of events is in fact predictable:

Everything that executes in the Promise constructor callback function will be executed at the time the promise is created, i.e. it runs synchronously before continuing with the code that follows new Promise().

Then the rest of the synchronous code executes until the call stack is empty.

Then, as part of microtasks appended to the current task, the asynchronous then callbacks are executed, in the order the then methods were previously executed.

The await keywords will also influence the order of execution: the async function in which they occur will "stop", letting the calling code continue synchronously as if the async function had executed a return (it returns a promise). Once the promise provided to await is resolved, the async function will get its state restored asynchronously (after the current executing code ran until the call stack is empty), and continue as part of a microtask. The timing for this is the same as for a then callback.

This explains your output. There is no randomness involved.

Example

Say, at the end of the first line of output, 900, what signals the execution to go back to af1()?

At the time 900 is output, there are two microtasks pending in the queue:

1) The then callback in af1 has finished execution, returning a value of undefined (as it has no return), and this represents the promised value that await is waiting for. This is pending for asynchronous treatment while the current code is still running until the call stack is empty.

2) The then callback in af2 has also finished execution: same principle.

As (1) is first in the microtask queue, this is what happens after 900 has been output and there is no more synchronous code to execute (the call stack is empty):

The JavaScript engine reads the microtask queue and takes the microtask to treat the first await: the function state is restored, including the state of its loop, and the loop continues. This means the promise constructor callback is executed synchronously, producing the first values you have in the second line of your output. ...etc.

Upvotes: 4

Related Questions