I have the following code in an HTML file:
const fooBar = function(resolve, reject) {
let flag = (Math.round(Math.random() * 10) % 2);
if(flag)
resolve({ "value": "foo", "rand": Math.random() });
else
reject({ "value": "bar", "rand": Math.random() });
};
const fooBarSuccess1 = function(value) {
console.log("Success 1:" + JSON.stringify(value));
};
const fooBarFailure1 = function(value) {
console.log("Failure 1:" + JSON.stringify(value));
};
const fooBarSuccess2 = function(value) {
console.log("Success 2:" + JSON.stringify(value));
};
const fooBarFailure2 = function(value) {
console.log("Failure 2:" + JSON.stringify(value));
};
new Promise(fooBar).then(fooBarSuccess1).catch(fooBarFailure1);
new Promise(fooBar).then(fooBarSuccess2, fooBarFailure2);
console.log("Before setting MicroTask.");
setTimeout(() => console.log("This Timeout was set before the MicroTask!"));
queueMicrotask(() => console.log("From MicroTask!"));
console.log("After setting MicroTask.");
When the Promise gets rejected, fooBarFailure1
is executed at the end of the microtask queue, so you might get the below output:
Before setting MicroTask. After setting MicroTask. Success 2:{"value":"foo","rand":0.3675094508130746} From MicroTask! Failure 1:{"value":"bar","rand":0.6828171208953322} This Timeout was set before the MicroTask!
However, shouldn't it be invoked before the code inside queueMicrotask
is executed? And I don't see any such issues with fooBarFailure2
. It gets executed in the expected order. The result is the same in Firefox 71 and Google Chrome 78. Can anybody explain what's happening here?
The difference is that fooBarFailure1
is further away from the root promise (the one from new Promise
) than fooBarFailure2
is. fooBarFailure1
isn't connected to the root promise, it's connected to the one created by .then(fooBarSuccess1)
:
new Promise(fooBar).then(fooBarSuccess1).catch(fooBarFailure1);
In contrast, fooBarSuccess2
and fooBarFailure2
are both attached to the root promise:
new Promise(fooBar).then(fooBarSuccess2, fooBarFailure2);
There's an internal rejection handler in the chain before fooBarFailure1
, but the fooBarFailure2
is hooked directly. That's what causes the extra async "tick".
Let's look at just the failure example, because it simplifies things:
const success = function(value) {
console.log("This never happens");
};
const fooBarFailure1 = function(value) {
console.log("Failure 1");
};
const fooBarFailure2 = function(value) {
console.log("Failure 2");
};
Promise.reject().then(success).catch(fooBarFailure1);
Promise.reject().then(success, fooBarFailure2);
console.log("Before setting MicroTask.");
setTimeout(() => console.log("This Timeout was set before the MicroTask!"));
queueMicrotask(() => console.log("From MicroTask!"));
console.log("After setting MicroTask.");
The output of that is:
Before setting MicroTask. After setting MicroTask. Failure 2 From MicroTask! Failure 1 This Timeout was set before the MicroTask!
Here's why:
Promise.reject()
returns a rejected promise in both cases.Promise.reject().then(success).catch(fooBarFailure1);
.then(success)
creates a new promise and hooks up fulfillment and rejection handlers; the rejection handler is internal and just passes on the rejection reason, since no rejection handler was supplied..catch(fooBarFailure1)
hooks up a rejection handler on the promise from then
.Promise.reject().then(success, fooBarFailure2);
:
then
hooks up both the fulfillment handler (success
) and the rejection handler (fooBarFailure2
) to the promise from Promise.reject()
"Before setting MicroTask."
is logged.setTimeout
queues its taskqueueMicrotask
queues its microtask"After setting MicroTask."
is logged.Promise.reject().then(success).catch(fooBarFailure1);
: That rejects the promise created by then
, queuing a microtask to call the rejection handler on the promise then
returned.Promise.reject().then(success, fooBarFailure2);
: That rejects the promise, calling fooBarFailure2
.
"Failure 2"
is logged.queueMicrotask
, which runs.
"From MicroTask!"
is logged.fooBarFailure1
, scheduled above, runs.
"Failure 1"
is logged."This Timeout was set before the MicroTask!"
is logged.