Reputation: 981
new Promise((resolve,reject) => {
console.log('outer promise')
resolve()
})
.then(() => {
console.log('outer 1 then')
new Promise((resolve,reject) => {
console.log('in promise')
resolve()
})
.then(() => {
console.log('in 1 then')
return Promise.resolve()
})
.then(() => {
console.log('in 2 then')
})
})
.then(() => {
console.log('outer 2 then')
})
.then(() => {
console.log('outer 3 then')
})
.then(() => {
console.log('outer 4 then')
})
Here are my explanation:
Execute the new Promise
, output outer promise
Execute the first outer then, its callback go to the microtask queue. The second outer then, the third outer then and the fourth outer then execute later, but all of their callback don't go to the queue since each promise returned by each then
still in pending state.
Execute the callback of the first outer then, and output outer 1 then
. After that, new Promise
will output in promise
Execute the first inner then, its callback go to the microtask queue. The callback of the second inner then don't go to the queue
Now, the callback of the first outer then has totally finished, which means the promise returned by the first outer then has resolved. Thus, the callback of the second outer then go to the queue
Execute the first task of the queue, it will output outer 2 then
and makes the callback of the third then go to the queue. Next, execute the callback of the second inner then, it will output in 2 then
Execute the callback of the third outer then, it will output outer 3 then
and makes the callback of the fourth then go to the queue.
At last, execute the callback of the fourth outer then, it will output outer 4 then
Therefore, the output order should be:
outer promise
outer 1 then
in promise
in 1 then
outer 2 then
in 2 then
outer 3 then
outer 4 then
But it actually output:
outer promise
outer 1 then
in promise
in 1 then
outer 2 then
outer 3 then
outer 4 then
in 2 then
What I am confused is why in 2 then
will output at last? And why outer 2 then
, outer 3 then
, outer 4 then
will output continuously? This is different from what I have thought about event loop. I thought the result is something to do with the statement return Promise.resolve()
, but I don't know what the statement exactly do and why it will influenced the output order.
Upvotes: 2
Views: 293
Reputation: 29282
Before explaining the output of your code, it is important to note that real-world code should not rely on the timing of promises in unrelated promise chains. If you want one promise to settle after/before another promise, instead of relying on the timing in which both of them will be resolved, create a promise chain that settles the promises in a sequential manner.
Having said that, the key to understanding the output of your code is to understand how the promise returned by the nested then()
method
...
.then(() => {
console.log('in 1 then')
return Promise.resolve()
})
...
is resolved.
Following steps explain the output:
As the executor function passed to the Promise
constructor is called synchronously, first thing that gets logged on the console is 'outer promise'
micro-task queue: [ ]
console output:
---------------
outer promise
After the above console.log
statement, resolve
function is called. As a result, the newly created promise is resolved synchronously.
This queues a job in the micro-task queue to execute the fulfilment handler passed to the then()
method.
micro-task queue: [ job('outer 1 then') ]
console output:
---------------
outer promise
After synchronous execution of the script ends, javascript can start processing the micro-task queue.
First job in the micro-task is dequeued and processed. As a result, 'outer 1 then'
and 'in promise'
are logged on the console.
micro-task queue: [ ]
console output:
---------------
outer promise
outer 1 then
in promise
As the nested promise is resolved as a result of resolve()
function call, a job is enqueued in the micro-task queue to execute its fulfilment handler.
...
.then(() => {
console.log('in 1 then');
return Promise.resolve();
})
...
micro-task queue: [ job('in 1 then') ]
console output:
---------------
outer promise
outer 1 then
in promise
As the callback function of the outer 1st then()
method ends, implicitly returning undefined
leads to the fulfilment of the promise returned by the wrapper then()
method. As a result, another job is enqueued in the micro-task queue to execute the outer 2nd then()
method's callback function
micro-task queue: [ job('in 1 then'), job('outer 2 then') ]
console output:
---------------
outer promise
outer 1 then
in promise
Next job in the micro-task queue is dequeued and processed. 'in 1 then'
is logged on the console and as the return value of the callback function is a promise, i.e. Promise.resolve()
, the promise returned by the wrapper inner then()
method is resolved to the promise returned by its callback function.
Following code example demonstrates how the promise returned by the then()
method is resolved to the promise returned by its callback function:
const outerPromise = new Promise((resolveOuter, rejectOuter) => {
const innerPromise = new Promise((resolveInner, rejectInner) => {
resolveInner();
});
// resolve/reject functions of the outer promise are
// passed to the `then()` method of the inner promise
// as the fulfilment/rejection handlers respectively
innerPromise.then(resolveOuter, rejectOuter);
});
This means that the outer promise (returned by the then()
method) now depends on the inner promise (return by its callback function). A job is enqueued in the micro-task queue to resolve the outer promise to the inner promise.
micro-task queue: [ job('outer 2 then'), job(resolve(outerPr, innerPr) ]
console output:
---------------
outer promise
outer 1 then
in promise
in 1 then
Next job in the micro-task queue is dequeued and processed. 'outer 2 then'
is logged on the console.
As the return value of the callback function is undefined
, the promise returned by the wrapper outer second then()
method is resolved. This enqueues a job in the micro-task queue to execute the fulfilment handler passed to the outer 3rd then()
method.
micro-task queue: [ job(resolve(outerPr, innerPr), job('outer 3 then') ]
console output:
---------------
outer promise
outer 1 then
in promise
in 1 then
outer 2 then
Next job in the micro-task queue is dequeued and processed.
As the inner promise is resolved, a job is enqueued in the micro-task queue to resolve the outer promise. (resolve function of the outer promise is registered as a fulfilment handler of the inner promise) (see step 5)
micro-task queue: [ job('outer 3 then'), job(resolve(outerPr) ]
console output:
---------------
outer promise
outer 1 then
in promise
in 1 then
outer 2 then
Next job in the micro-task queue is dequeued and processed. 'outer 3 then'
is logged on the console. A job is enqueued in the micro-task queue to execute the fulfilment handler passed to the 4th outer then()
method.
micro-task queue: [ job(resolve(outerPr), job('outer 4 then') ]
console output:
---------------
outer promise
outer 1 then
in promise
in 1 then
outer 2 then
outer 3 then
Next job in the micro-task queue is dequeued and processed. This resolves the promise returned by the 1st inner then()
method. As a result, a job is enqueued in the micro-task queue to execute the fulfilment handler passed to the 2d inner then()
method.
micro-task queue: [ job('outer 4 then'), job('in 2 then') ]
console output:
---------------
outer promise
outer 1 then
in promise
in 1 then
outer 2 then
outer 3 then
Finally, the last two jobs in the micro-task queue are dequeued and processed one after the other, logging 'outer 4 then'
and 'in 2 then'
on the console respectively.
micro-task queue: [ ]
console output:
---------------
outer promise
outer 1 then
in promise
in 1 then
outer 2 then
outer 3 then
outer 4 then
in 2 then
Upvotes: 2
Reputation: 21120
I would shove this in the corner undefined behaviour. You shouldn't rely on the internals of the JavaScript engine to execute then
callbacks in a certain order. I can imagine two scenarios that suit the given code.
The inner tasks should be run before the outer tasks. Here you should make sure you return the inner promise. You can then move the inner then
calls and add them to the outer promise chain.
new Promise((resolve,reject) => {
console.log('outer promise');
resolve();
})
.then(() => {
console.log('outer 1 then');
return new Promise((resolve,reject) => { // <- added a return statement
console.log('in promise');
resolve();
});
})
.then(() => {
console.log('in 1 then');
return Promise.resolve();
})
.then(() => {
console.log('in 2 then');
})
.then(() => {
console.log('outer 2 then');
})
.then(() => {
console.log('outer 3 then');
})
.then(() => {
console.log('outer 4 then');
});
You want to split off a path for the inner promise and don't care that further then
callbacks execute before or after this separate path. The execution order between the two different paths should not be assumed. However within a path you can control the order. In the example below in 2 then
should always be executed after in 1 then
. The order of in 1 then
and outer 2 then
should not be assumed.
const splitPoint = new Promise((resolve,reject) => {
console.log('outer promise');
resolve();
})
.then(() => {
console.log('outer 1 then');
return new Promise((resolve,reject) => { // <- added a return statement
console.log('in promise');
resolve();
});
});
const path1 = splitPoint.then(() => {
console.log('in 1 then');
return Promise.resolve();
})
.then(() => {
console.log('in 2 then');
});
const path2 = splitPoint.then(() => {
console.log('outer 2 then');
})
.then(() => {
console.log('outer 3 then');
})
.then(() => {
console.log('outer 4 then');
});
Promise.all([path1, path2]).then(() => {
console.log('path1 and path2 finished');
});
Upvotes: 0