我们可以通过进行小幅改写并添加日志记录来使其更加清晰:
async function run(callback) {
let then = callback;
for (let i = 1; i <= 10; i++) {
console.log(callback === run ? "A" : "B", i);
then = await { then };
}
}
run(run);
.as-console-wrapper { max-height: 100% !important; }
这表明实际上有两个循环开始了。为简单起见,称其为 A 和 B。它们记录并 await
,这意味着它们的日志交错并导致 A1、B1、A2、B2 等。
这是因为第一条语句:run(run)
。它将相同的函数作为回调函数传递给自身。这不会 调用 回调函数,但这是解开它的第一步。
理解正在发生的事情的下一步是 await
。你可以 await
任何值,在 大多数情况下,如果它不是一个 promise,那就无所谓了。如果你有 await 42;
它只是假装这个值是 Promise.resolve(42)
,并立即在下一个 tick 中继续操作。对于 大多数 非 promises 来说,这是正确的。唯一的例外是 thenables——具有 .then()
方法的对象。
当等待 thenable 时,它的 then()
方法会被调用:
const thenable = {
then() {
console.log("called");
}
};
(async () => {
await thenable;
})()
这就解释了await { then }
语句,它使用了{ then: then }
的简写形式,其中then
是传递给run
的回调函数。因此,它创建了一个thenable对象,当被await时,将执行回调函数。
这意味着第一次执行run()
并在循环A的第一次迭代中,代码实际上是await { then: run }
,它将再次执行run
,然后启动循环B。
每次都会覆盖then
的值,因此它只能同时运行两个循环,而不是更多。
理解这段代码还涉及到thenable的更多内容。我之前展示了一个简单的例子,仅说明等待它会调用该方法。但实际上,await thenable
会使用两个参数调用.then()
- 成功和失败的函数。与Promise构造函数类似。
const badThenable = {
then() {
console.log("bad called");
}
};
(async () => {
await badThenable;
console.log("never reached");
})();
const goodThenable = {
then(resolve, reject) {
console.log("good called");
resolve();
}
};
(async () => {
await goodThenable;
console.log("correctly reached");
})();
这很重要,因为
run()
期望回调,当执行
await { then: run }
时,它会调用
run(builtInResolveFunction)
,然后将其传递给下一个
await { then: builtInResolveFunction }
,反过来又会解决导致
await
解决。
总之,交错的日志记录只是任务解析方式的一个因素。
(async () => {
for (let i = 1; i <= 10; i++){
console.log("A", i);
await Promise.resolve("just to force a minimal wait");
}
})();
(async () => {
for (let i = 1; i <= 10; i++) {
console.log("B", i);
await Promise.resolve("just to force a minimal wait");
}
})();
如果有两个异步函数正在运行且没有什么需要真正等待的话:
- 其中一个将一直运行,直到它遇到
await
,然后暂停。
- 另一个将一直运行,直到它遇到
await
,然后暂停。
- 重复1和2,直到没有更多的
await
。
run(run);
相关。 - VLAZawait
对于这段代码的操作至关重要。 - VLAZ