为什么Promise库使用事件循环?

Why do Promise libraries use event loops?

本文关键字:事件 循环 Promise 为什么      更新时间:2023-10-16

考虑以下JavaScript代码:

var promise = new Promise();
setTimeout(function() {
    promise.resolve();
}, 10);
function foo() { }
promise.then(foo);

在我所见过的承诺实现中,promise.resolve()会简单地设置一些属性来表明承诺被解析,然后在事件循环期间调用foo(),但似乎promise.resolve()会有足够的信息来立即调用任何延迟的函数,如foo()。

事件循环方法似乎会增加复杂性并降低性能,那么为什么要使用它?

虽然我大部分使用的承诺是与JavaScript,我的问题的部分原因是在实现承诺非常性能密集型的情况下,如c++游戏,在这种情况下,我想知道我是否可以利用承诺的一些好处没有事件循环的开销。

所有的承诺实现,至少好的都是这样做的。

这是因为在异步API中混合同步会释放Zalgo。

承诺有时不会立即解决,有时会延迟解决,这意味着API是一致的。否则,将在执行顺序中得到未定义的行为。

function getFromCache(){
      return Promise.resolve(cachedValue || getFromWebAndCache());
}
getFromCache().then(function(x){
     alert("World");
});
alert("Hello");

承诺库延迟的事实意味着上面块的执行顺序是有保证的。在像jQuery这样的失信实现中,顺序取决于是否从缓存中获取条目。这很危险。

不确定的执行顺序是非常危险的,也是bug的常见来源。Promises/A+规范把你扔进了成功的坑里。

promise.resolve()是否同步或异步执行其延续实际上取决于实现。

此外,"事件循环"并不是提供不同"执行上下文"的唯一机制。可能还有其他方法,例如线程或线程池,或者考虑GCD (Grand Central Dispatch,调度库),它提供调度队列。

Promises/A+ Spec明确要求延续(onFulfilled分别是onRejected处理程序)将在调用then方法的"执行上下文"中异步执行。

    在执行上下文堆栈只包含平台代码之前,不能调用
  1. onFulfilledonRejected。[3.1]。

在注释下面你可以读到它的实际意思:

这里的"平台代码"是指引擎、环境和承诺实现代码。在实践中,这个要求确保onfulfillment和onRejected在调用then的事件循环之后异步执行,并且使用一个新的堆栈。

在这里,每个事件将在不同的"执行上下文"中执行,即使这是相同的事件循环和相同的"线程"。

由于Promises/A+规范是为Javascript环境编写的,一个更通用的规范将只要求延续相对于调用then方法的调用者异步执行

这样做有很好的理由!

示例(伪代码):
promise = async_task();
printf("a");
promise.then((int result){
    printf("b");
});
printf("c");

假设处理程序(延续)将在与调用站点相同的线程上执行,执行顺序应该是控制台显示的:

acb
特别是,当承诺已经被解决时,一些实现倾向于在相同的执行上下文中"立即"调用延续(即同步地)。这显然违反了上述规则。

规则总是异步地调用延续的原因是调用站点需要保证处理程序和then后面的代码的相对执行顺序,包括在任何场景中的延续语句。也就是说,无论promise是否已经被解析,语句的执行顺序必须是相同的。否则,更复杂的异步系统可能无法可靠地工作。

另一个糟糕的设计选择是在其他语言中有多个同时执行上下文的实现——比如多线程环境(在JavaScript中不相关,因为只有一个执行线程),是关于resolve函数的延续将被同步地调用。当异步任务将在稍后的事件循环周期中完成时,这甚至会出现问题,因此延续将确实相对于调用站点异步执行

然而,当异步任务完成后调用resolve函数时,该任务可能在私有执行上下文中执行(例如"工作线程")。这个"工作线程"通常是一个专用的,也可能是特殊配置的执行上下文——然后调用resolve。如果resolve函数将同步地执行延续,那么延续将在任务的私有执行上下文中运行——这通常是不希望的。

承诺都是关于合作的多任务处理。

实现这一目标的唯一方法是使用基于消息的调度。

计时器(通常具有0延迟)仅用于将任务/消息发布到消息队列中- yield-to-next-task-in- queue范式。因此,由小事件处理程序组成的整个结构可以正常工作,并且您产生的频率越高,所有这些工作就越顺利。