The difference between Code#1 and Code#2 is: Code#1 uses resolve(p)
and Code#2 uses p.then(()=>resolve())
. I would expect the sequence of output to be invariant, but they generate a different sequence. I cannot figure out why.
Code #1: resolve(p)
const p = Promise.resolve();
new Promise((resolve) => {
resolve(p); // <---
}).then(() => {
console.log('after:await');
});
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
.then(() => console.log('tick:c'));
Output:
tick:a
tick:b
after:await
tick:c
Code #2: p.then(()=>resolve())
const p = Promise.resolve();
new Promise((resolve) => {
p.then(()=>resolve()); // <---
}).then(() => {
console.log('after:await');
});
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
.then(() => console.log('tick:c'));
Output:
tick:a
after:await
tick:b
tick:c
Why is the order of output different?
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".
In both your answers promise chain1 and promise chain2 can be interleaved differently. But, tick:a, tick:b, tick:c will be outputted in that order, tick:a before tick:b, and tick:b before tick:c. after:await can be outputted anywhere in between.
For what your code is doing.
// Returns a resolved promise object
// Which is equivalent to const p = new Promise(resolve => resolve());
const p = Promise.resolve();
// For Reference Call This Promise Chain 1
new Promise((resolve) => {
// Fulfills the promise with the promise object p
resolve(p); // (1)
}).then(() => {
console.log('after:await');
});
For Reference Promise Chain 2
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
.then(() => console.log('tick:c'));
const p = Promise.resolve();
new Promise((resolve) => {
// Here you are calling then which if promise p has been fulfilled
// will call the callback you passed as an argument, which then
// will eventually cause the outer promise to enter a state of
// fulfilled triggering a call to the next 'then' provided in the part of the chain.
p.then(()=>resolve());
}).then(() => {
console.log('after:await');
});
p.then(() => console.log('tick:a'))
.then(() => console.log('tick:b'))
.then(() => console.log('tick:c'));
The Promise.resolve() method returns a Promise
object that is resolved with a given value. If the value is a promise, that promise is returned; if the value is a thenable (i.e. has a "then" method
), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value. This function flattens nested layers of promise-like objects (e.g. a promise that resolves to a promise that resolves to something) into a single layer.
Please refer here for more info about Promise.resolve().
The difference in the output of both of your codes is due to the fact that the then
handlers are called asynchronously.
When using a resolved promise, the 'then' block will be triggered
instantly, but its handlers will be triggered asynchronously.
Please refer here for more info about then
handlers' behaviour.
A Promise is in one of these states:
-pending: initial state, neither fulfilled nor rejected.
-fulfilled: meaning that the operation completed successfully.
-rejected: meaning that the operation failed.
A pending promise can either be fulfilled with a value, or rejected with a reason (error). When either of these options happens, the associated handlers queued up by a promise's then method are called.
Refer this for more details
Now in your particular case, you are using "Promise.resolve()" probably to create a new promise object but what it does is that it creates an already resolved promise with no value. So your promise object "p" is resolved during its creation and the rest of the code where you resolve it has literally no effect other than putting the "after:wait" into a handler queue. Please refer to the Event Loop with Zero Delay. The output for both the codes is different based on when the "console.log" is put in the call stack and not because how you are writing it.
The correct way of doing this can be:
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
});
promise1.then(function(value) {
console.log(value);// expected output: "foo"
});
console.log(promise1);// expected output: [object Promise]