For the below code
function inner () {
new Promise(function(resolve,reject){
resolve()
}).then(function(){
console.log(\'Inner Promise\')
})
}
In a nutshell, you get the behavior you see because the .then()
method on the inner()
promise runs first before the .then()
method on the outer()
promise and thus it's handler gets queued first (see step by step explanation below for why this is).
What does Promise resolve do internally?
resolve()
changes the internal state of the promise to Fulfilled. At that moment, if there are any .then()
handlers already attached to the promise, they are added to a queue to be executed when the stack unwinds and the current running path of Javascript finishes and returns control back to the system. Note, as you will see in this case (when you read the step-by-step analysis below), if there are not yet any .then()
handlers that have been registered, nothing can yet be added to the queue.
I thought the outer resolve would be the first to enter the JS Message Queue followed by the inner resolve. However the JS Event Loop fires the Inner resolve first then followed by Outer resolve.
Promise resolve actions are not added to the queue. resolve()
is synchronous. It changes the state of the current promise to the Fulfilled state immediately. If, at the time the promise is resolved, there are any .then()
handlers already register, then they are what is added to a queue. But, in both your promises, at the moment each of your promises are resolved, there are no .then()
handlers yet attached. So, those .then()
handlers won't be queued at the point the promise is resolved. Instead, they will be queued later when the .then()
method actually runs and registers them.
Here's a bit of an analysis of how your code runs and a likely explanation:
outer()
. This creates a Promise object and synchronously calls the promise executor callback you pass it. resolve()
which will queue up the calling of any currently attached .then()
handlers. Note, that at the moment you call resolve()
, there are no .then()
handlers yet because in this code outer().then()
, you're still running outer()
and the .then()
after it has not yet run so there isn't actually yet anything to queue up yet (this is probably key to the ordering you observe - read on for further details).inner()
. That creates a new promise and then (still running synchronously) calls the promise executor callback you pass there which calls resolve()
. Again, there are not yet any .then()
handlers attached so there is still yet nothing else to schedule for future execution.inner()
returns and the .then()
method is called on that promise inside of inner()
. This promise has already been resolved so, when this .then()
handler is called, the promise knows to schedule it to run in the future. Since all .then()
handlers are called asynchronously when the stack has unwound to only platform code, it is not run immediately, but it is scheduled to run in the future by puttiing it in a queue. It is implementation dependent exactly how this queue works (macro task or micro task, etc...), but we know it is guaranteed by the Promise specification to run after the current synchronous piece of JS code that is executing finishes running and returns control back to the system.inner()
returns (code is still running synchronously).outer()
returns and the .then()
method in outer().then()
runs. Just like in the previous example, when this .then()
method is called, the host promise is already resolved. So, the promise engine will schedule the .then()
handler callback to be run by adding it to the queue..then()
handlers in steps 4 and 6 are queued in the order they were run (which would be the logical implementation), then you would see the .then()
handler on inner()
run first and then the .then()
handler on outer()
would run since inner().then() ran first before
outer().then()`. That is what you observe.outer()
is resolved before inner()
is, at the time outer()
is resolved, there are not .then()
handlers attached so there is nothing to schedule for future execution when it is resolved. This is likely why even though it is resolved first, its .then()
handlers don't run first. Once both inner()
and outer()
are resolved, it is inner's .then()
method that runs first, so it gets first crack at scheduling a .then()
handler to run and this is what you observe.You can get some additional context for what's going on by reading and studying these references:
What is the order of execution in javascript promises
Difference between microtask and macrotask within an event loop context.
If you wanted to more explicitly specify that the inner .then()
handler would fire first, you can simply chain it to the outer()
promise like this:
function inner () {
return new Promise(function(resolve,reject){
resolve();
}).then(function(){
console.log('Inner Promise')
})
}
function outer() {
// Add return here to chain the inner promise
// make to make sure that outer() does not resolve until
// inner() is completely done
return inner();
}
outer().then(function(data) {
console.log('Outer Promise')
})
If you wanted to guarantee that the outer().then()
handler was called first, you'd have to pick a different structure since this structure does not force that type of order in any way and can't be cajoled that direction unless you consciously delay the running of inner()
(using a setTimeout()
or some such thing) or restructure the code. For example, if you really wanted to restructure to force inner()
to run last, you would kick it off in the outer().then()
handler like this:
function inner () {
return new Promise(function(resolve,reject){
resolve()
}).then(function(){
console.log('Inner Promise')
})
}
function outer() {
return new Promise(function(resolve, reject){
resolve()
})
}
outer().then(function(data) {
console.log('Outer Promise')
return inner();
})
I thought the outer resolve would be the first to enter the JS Message Queue followed by the inner resolve.
Yes, the "outer" promise is resolved first. Put a console.log
right next to the resolve
call.
But no, the outer then callback is not put in the queue first because it installed after the inner then callback. What you are doing is essentially equivalent to
var outer = Promise.resolve();
var inner = Promise.resolve();
inner.then(function() {
console.log('Inner Promise')
});
outer.then(function(data) {
console.log('Outer Promise')
});
but obfuscated due to the nested (synchronous) function calls.