This is in fact a very interesting question, because the Promise/A+ specs would allow the first code version to produce the same output as the second version of the code.
One could dismiss the question saying the Promise implementation says nothing about how resolve(p)
would be implemented. This is a true statement when looking at the Promise/A+ specification, quoting from its preface:
the core Promises/A+ specification does not deal with how to create, fulfill, or reject promises, ...
But the EcmaScript specification for Promises (Section 25.4) is quite more detailed than the Promise/A+ specification and requires that "jobs" are added to the back of the relevant job queue -- which for promise settlements is the PromiseJobs queue (25.4.1.3.2 and 8.4): this determines a specific order:
Required Job Queues
[...]
PromiseJobs: Jobs that are responses to the settlement of a Promise
[...]
The PendingJob records from a single Job Queue are always initiated in FIFO order
It also defines that resolve(p)
-- when p
is a thenable -- will first put a job on the queue that will perform the necessary internal call of the p.then
method. This is not done immediately. To quote the note in the EcmaScript specs at 25.4.2.2:
This process must take place as a Job to ensure that the evaluation of the then
method occurs after evaluation of any surrounding code has completed.
This statement is illustrated with the order of output in the following snippet:
const p1 = Promise.resolve();
// Wrap the `p1.then` method, so we can log something to the console:
const origThen = p1.then;
p1.then = function(...args) {
console.log("The p1.then method is called asynchronously when triggered by resolve(p1)");
origThen.call(this, ...args);
};
const p2 = new Promise(resolve => {
resolve(p1);
console.log("Code that follows is executed synchronously, before p1.then is");
});
When we use the p1.then(resolve)
method call instead of resolve(p1)
, we get the opposite order:
const p1 = Promise.resolve();
// Wrap the `p1.then` method, so we can log something to the console:
const origThen = p1.then;
p1.then = function(...args) {
console.log("The p1.then method is called synchronously now");
origThen.call(this, ...args);
};
const p2 = new Promise(resolve => {
p1.then(resolve);
console.log("Code that follows is executed synchronously, after p1.then is");
});
Your code
The above really explains the different order of output you get. Here is how the first code version sequences the actions. First let me rewrite this a bit, so that most involved promises have a name:
const p1 = Promise.resolve();
const p2 = new Promise((resolve) => resolve(p1));
const p3 = p2.then(() => console.log('after:await'));
const p4 = p1.then(() => console.log('tick:a'));
const p5 = p4.then(() => console.log('tick:b'))
const p6 = p5.then(() => console.log('tick:c'));
Now, after the main, synchronous code has executed to completion, only p1
has a resolved state, and two jobs are present on the job queue (micro tasks queue), one as a result of resolve(p1)
and a second one because of p1.then
:
According to 25.4.2.2,
the then
method of p1
is called passing it the internal [[resolve]]
function related to p2
. The p1.then
internals know that p1
is resolved and put yet another job on the queue to actually resolve p2
!
The callback with "tick:a" is executed, and promise p4 is marked as fulfilled, adding a new job in the job queue.
There are now 2 new jobs in the queue, which are processed in sequence:
The job from step 1 is executed: p2 is now resolved. This means a new job is queued to actually call the corresponding then
callback(s)
The job from step 2 is executed: the callback with "tick:b" is executed
Only later the job added in step 3 will be executed, which will call the callback with "after:await".
So, in conclusion. In EcmaScript a resolve(p)
, where p
is a thenable involves an asynchronous job, which itself triggers yet another asynchronous job to notify the fulfilment.
The then
callback, that differentiates the second code version, will only need one asynchronous job to get called, and thus it happens before the output of "tick:b".