为什么 .catch 回调在微任务队列的末尾执行?
Why are .catch callbacks executed at the end of the microtask queue?
我在 HTML 文件中有以下代码:
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.");
当 Promise 被拒绝时,fooBarFailure1
在微任务队列的末尾执行,所以你可能会得到以下输出:
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!
但是,在queueMicrotask
里面的代码执行之前,不应该调用它吗?而且我没有看到 fooBarFailure2
有任何此类问题。它以预期的顺序执行。结果在 Firefox 71 和 Google Chrome 78 中是一样的。有人能解释一下这里发生了什么吗?
不同之处在于 fooBarFailure1
比 fooBarFailure2
离根承诺(来自 new Promise
的承诺)更远。 fooBarFailure1
未连接到根承诺,它连接到由 .then(fooBarSuccess1)
:
创建的承诺
new Promise(fooBar).then(fooBarSuccess1).catch(fooBarFailure1);
相比之下,fooBarSuccess2
和 fooBarFailure2
都 附加到根承诺:
new Promise(fooBar).then(fooBarSuccess2, fooBarFailure2);
在 fooBarFailure1
之前的链中有一个内部拒绝处理程序,但 fooBarFailure2
是直接挂钩的。这就是导致额外异步 "tick".
的原因
让我们只看失败示例,因为它简化了事情:
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.");
其输出为:
Before setting MicroTask.
After setting MicroTask.
Failure 2
From MicroTask!
Failure 1
This Timeout was set before the MicroTask!
原因如下:
Promise.reject()
returns 在这两种情况下都是被拒绝的承诺。
- 在
Promise.reject().then(success).catch(fooBarFailure1);
.then(success)
创建一个新的承诺并连接履行和拒绝处理程序;拒绝处理程序是内部的,只是传递拒绝原因,因为没有提供拒绝处理程序。
.catch(fooBarFailure1)
根据 then
. 的承诺连接拒绝处理程序
- 由于 promise 被拒绝,它使用微任务调用附加到它的拒绝处理程序。
- 在
Promise.reject().then(success, fooBarFailure2);
中:
then
将履行处理程序 (success
) 和拒绝处理程序 (fooBarFailure2
) 连接到来自 Promise.reject()
的承诺
- 由于 promise 被拒绝,它使用微任务调用附加到它的拒绝处理程序。
"Before setting MicroTask."
已记录。
setTimeout
对其任务进行排队
queueMicrotask
将其微任务排队
"After setting MicroTask."
已记录。
- 任务调度到此结束,微任务处理开始
- 第一个微任务正在处理来自
Promise.reject().then(success).catch(fooBarFailure1);
的拒绝:拒绝由 then
创建的承诺,排队一个微任务以在返回的承诺 then
上调用拒绝处理程序。
- 第二个微任务正在处理来自
Promise.reject().then(success, fooBarFailure2);
的拒绝:拒绝承诺,调用 fooBarFailure2
。
"Failure 2"
已记录。
- 第三个微任务来自
queueMicrotask
,它正在运行。
"From MicroTask!"
已记录。
- 上面安排的对
fooBarFailure1
的微任务调用运行。
"Failure 1"
已记录。
- 下一个任务运行,调用定时器回调
"This Timeout was set before the MicroTask!"
已记录。
我在 HTML 文件中有以下代码:
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.");
当 Promise 被拒绝时,fooBarFailure1
在微任务队列的末尾执行,所以你可能会得到以下输出:
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!
但是,在queueMicrotask
里面的代码执行之前,不应该调用它吗?而且我没有看到 fooBarFailure2
有任何此类问题。它以预期的顺序执行。结果在 Firefox 71 和 Google Chrome 78 中是一样的。有人能解释一下这里发生了什么吗?
不同之处在于 fooBarFailure1
比 fooBarFailure2
离根承诺(来自 new Promise
的承诺)更远。 fooBarFailure1
未连接到根承诺,它连接到由 .then(fooBarSuccess1)
:
new Promise(fooBar).then(fooBarSuccess1).catch(fooBarFailure1);
相比之下,fooBarSuccess2
和 fooBarFailure2
都 附加到根承诺:
new Promise(fooBar).then(fooBarSuccess2, fooBarFailure2);
在 fooBarFailure1
之前的链中有一个内部拒绝处理程序,但 fooBarFailure2
是直接挂钩的。这就是导致额外异步 "tick".
让我们只看失败示例,因为它简化了事情:
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.");
其输出为:
Before setting MicroTask. After setting MicroTask. Failure 2 From MicroTask! Failure 1 This Timeout was set before the MicroTask!
原因如下:
Promise.reject()
returns 在这两种情况下都是被拒绝的承诺。- 在
Promise.reject().then(success).catch(fooBarFailure1);
.then(success)
创建一个新的承诺并连接履行和拒绝处理程序;拒绝处理程序是内部的,只是传递拒绝原因,因为没有提供拒绝处理程序。.catch(fooBarFailure1)
根据then
. 的承诺连接拒绝处理程序
- 由于 promise 被拒绝,它使用微任务调用附加到它的拒绝处理程序。
- 在
Promise.reject().then(success, fooBarFailure2);
中:then
将履行处理程序 (success
) 和拒绝处理程序 (fooBarFailure2
) 连接到来自Promise.reject()
的承诺
- 由于 promise 被拒绝,它使用微任务调用附加到它的拒绝处理程序。
"Before setting MicroTask."
已记录。setTimeout
对其任务进行排队queueMicrotask
将其微任务排队"After setting MicroTask."
已记录。- 任务调度到此结束,微任务处理开始
- 第一个微任务正在处理来自
Promise.reject().then(success).catch(fooBarFailure1);
的拒绝:拒绝由then
创建的承诺,排队一个微任务以在返回的承诺then
上调用拒绝处理程序。 - 第二个微任务正在处理来自
Promise.reject().then(success, fooBarFailure2);
的拒绝:拒绝承诺,调用fooBarFailure2
。"Failure 2"
已记录。
- 第三个微任务来自
queueMicrotask
,它正在运行。"From MicroTask!"
已记录。
- 上面安排的对
fooBarFailure1
的微任务调用运行。"Failure 1"
已记录。
- 第一个微任务正在处理来自
- 下一个任务运行,调用定时器回调
"This Timeout was set before the MicroTask!"
已记录。