Reputation: 9279
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
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.
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