Promise resolve() 已调用但在调用它的循环终止之前不会解析

Promise resolve() called but does not resolve until loop it was called from terminates

我正在基于游戏循环在 nodejs 中编写应用程序。循环的每次迭代,它都会触发一个事件发射器,并调用一个更新方法,如下所示:

  updateLoop() {
    while (!this.windowShouldClose()) {
      this.onUpdate.emit();
      this.update();
    }
  }

似乎事件发射器可以帮助在游戏对象上编写异步函数,这些游戏对象在不同操作之间等待帧,就像协程一样。我写了一个实用函数,它会回调,或者在事件发射器的下一次发射时解决一个承诺:

nextFrame() {
  return new Promise((resolve) => {
    this.onUpdate.once(resolve); // event emitter will resolve promise on next emit()
  })
}

nextFrameCallback(callback) {
  this.onUpdate.once(callback);
}

// example use
async doThingsAsync() {
  // do something
  await this.nextFrame();
  // do something else on the next frame / loop iteration
}

虽然基于回调的函数按预期工作,但 nextFrame() 的承诺版本并未在我预期的时间解决承诺。 await nextFrame() 仅在 updateLoop() 的外循环退出后才在我的示例中解析。我附加了一个 console.log 到承诺以了解更多信息,并发现控制台日志和 resolve() 确实在循环中被调用,但等待它仍然会等待循环完全终止。

nextFrameDebug() {
  return new Promise((resolve) => {
    this.onUpdate.once(() => {
      console.log('debug nextFrame'); // this prints during the expected loop iteration
      resolve(); // this does not actually resolve until updateLoop() terminates
    })
  })
}

这是一个演示上述功能的 JSFiddle:https://jsfiddle.net/8L4wub29/5/ 似乎我接近一个功能性解决方案,但我对承诺或异步功能有一些误解。这与从循环内调用异步函数有关吗?我如何编写 nextFrame() 以便承诺在循环的下一次迭代时解决,而不是在循环退出后解决?我意识到对于游戏中的大多数功能,毫秒级的超时更有用,但在某些情况下,例如等待物理更新,游戏可能只想等待一帧作为实用函数。基于回调的版本工作正常,但如果你需要多次使用它,那么你需要嵌套它,这看起来不像使用 await

那样干净

Promise 保证其 .then 回调将 始终 被异步调用。这意味着如果你有一些随机的承诺,并且你做了这样的事情:

somePromise.then(() => {
  console.log('inside');
});
console.log('outside');

即使 promise 恰好已经处于已解决状态,您也会始终看到“外部”和“内部”。 Promise 以这种方式设计,因此您可以确定执行顺序,因此不必修复涉及两个顺序的痛苦错误。当您使用 await 而不是显式 .then 时也是如此:await 之后的代码必须等到 promise 解析,并且调用堆栈 returns 到系统代码。

因此,当您从回调切换到 Promise 时,您从可以在循环中间同步调用回调的代码变成了必须等待执行的代码 return。只有一次 updateLoop returns 或 yield 可以在 promise 运行 之后的代码。由于该循环从不 returns 或 yield 直到它全部结束,因此所有承诺都会延迟到循环之后。

如果您需要 updateLoop 到 运行 同步,那么您不能为此使用 promises。相反,如果 updateLoop 应该是异步的(可能在 运行 下一步之前设置超时),那么可以使用 promises,但我需要更多关于你想要做什么的细节举个例子。

回答我自己的问题,因为我找到了提供预期功能的东西。我只是将示例中的 updateLoop() 调用更新为此。

  async updateLoop() {
    while (!this.windowShouldClose()) {
      await null;
      this.onUpdate.emit();
      this.update();
    }
  }

是否有人能够更清楚地评论为什么这现在会产生预期的效果?除非我添加 await 语句,否则将函数更改为 async 不会影响 promise 解析的点。我假设包含 await 语句会释放最终允许承诺解决的事件循环。 如果您使用此功能编辑上述 JSfiddle,控制台中打印的数字将与打印语句“预期”的数字不一致 - 这只与计数器递增有关。执行顺序似乎是正确的。