为什么这个循环每次迭代重复两次?

Leo*_*nor 5 javascript for-loop repeat async-await

下面的函数将每个数字打印两次。有人可以解释它是如何工作的吗?我尝试调试,但我所看到的是 的值i仅在每第二次迭代时增加。

async function run(then) {
    for (let i = 1; i <= 10; i++) {
        console.log(i);
        then = await { then };
    }
}

run(run);
Run Code Online (Sandbox Code Playgroud)

具体来说,有两点我不明白。

  • 为什么i每次迭代都不增加?
  • 具体是then = await { then };做什么的?我的第一个猜测是,它会等待嵌套异步调用run完成,然后再继续下一次迭代,但情况似乎并非如此。

VLA*_*LAZ 8

我们可以通过小的重写来使这一点更清楚以包括日志记录:

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);
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100% !important; }
Run Code Online (Sandbox Code Playgroud)

这表明实际上启动了两个循环。为简单起见,简称为 A 和 B。它们记录日志,await这意味着它们的日志交错并导致 A 1、B 1、A 2、B 2 等。

发生这种情况是因为第一个语句:run(run)。它将相同的函数作为回调传递给自身。这不会调用回调,但这是解决这个问题的第一步。


了解正在发生的事情的下一步是await。你可以设置await任何值,在大多数情况下,如果它不是承诺,那也没关系。如果你有await 42;它,就假装该值是Promise.resolve(42),并在下一个价格变动时立即继续操作。对于大多数非承诺来说都是如此。唯一的例外是thenables——.then()方法的对象。

当等待一个 thenable 时,它​​的then()方法被调用:

const thenable = {
  then() {
    console.log("called");
  }
};

(async () => {
  await thenable;
})()
Run Code Online (Sandbox Code Playgroud)

然后解释了该await { then }声明。这使用回调传递到{ then: then }何处的简写。因此,它创建了一个 thenable 对象,当等待时,它将执行回调。thenrun

这意味着第一次run()被执行,并且在循环 A 的第一次迭代中,代码是有效的,await { then: run }它将再次执行run,然后启动循环 B。

每次都会覆盖的值then,因此它只并行运行两个循环,而不是更多。


还有更多与完全掌握此代码相关的内容。我之前展示了一个简单的例子,它只是显示等待它调用该方法。然而,实际上await thenable.then()使用两个参数进行调用 - 可以在成功和失败时调用的函数。与 Promise 构造函数的方式相同。

const badThenable = {
  then() {
    console.log("bad called");
  }
};

(async () => {
  await badThenable;
  console.log("never reached");
})();

const goodThenable = {
  then(resolve, reject) { //two callbacks
    console.log("good called");
    resolve(); //at least one needs to be called
  }
};

(async () => {
  await goodThenable;
  console.log("correctly reached");
})();
Run Code Online (Sandbox Code Playgroud)

这是相关的,因为run()需要一个回调,并且当await { then: run }执行时它会调用它run(builtInResolveFunction),然后将其传递给下一个await { then: builtInResolveFunction },而下一个解析又会导致 aawait解析。


撇开所有这些不谈,交错日志记录只是任务如何解决的一个因素:

(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");
  } 
})();
Run Code Online (Sandbox Code Playgroud)

如果有两个异步函数正在运行并且没有什么可等待的:

  1. 一个将运行直到达到 a await,然后将被暂停。
  2. 另一个将运行直到达到 a await,然后将被暂停。
  3. 重复 1. 和 2.,直到不再有等待。

  • @Ivar 实际上还有更多。但现在是睡觉时间了。明天对我来说有点忙。我会尽力尽快添加。本质上,A 和 B 互相扔掉解析器函数并在其上调用“await”*解析另一个“await”*并允许另一个循环继续。因此,例如,对于 A 中的“i = 3”,“await { then }”将 B 中的“await { then }”解析为“i = 2”。这种来回不仅允许整个事情正常进行,还意味着对于 B `i = 10` 来说,await 永远不会解析,因为 A 已经完成。我想为其绘制一个序列图以便更好地说明。 (2认同)