[英]Constructor of a custom promise class is called twice (extending standard Promise)
I'm playing with Promise Extensions for JavaScript (prex) and I want to extend the standard Promise class with cancellation support using prex.CancellationToken , complete code here .我正在使用Promise Extensions for JavaScript (prex)并且我想使用prex.CancellationToken扩展具有取消支持的标准Promise 类,完整代码请点击此处。
Unexpectedly, I'm seeing the constructor of my custom class CancellablePromise
being called twice.出乎意料的是,我看到我的自定义类
CancellablePromise
的构造函数被调用了两次。 To simplify things, I've now stripped down all the cancellation logic and left just a bare minimum required to repro the issue:为了简化事情,我现在已经剥离了所有取消逻辑,只留下重现问题所需的最低限度:
class CancellablePromise extends Promise { constructor(executor) { console.log("CancellablePromise::constructor"); super(executor); } } function delayWithCancellation(timeoutMs, token) { // TODO: we've stripped all cancellation logic for now console.log("delayWithCancellation"); return new CancellablePromise(resolve => { setTimeout(resolve, timeoutMs); }, token); } async function main() { await delayWithCancellation(2000, null); console.log("successfully delayed."); } main().catch(e => console.log(e));
Running it with node simple-test.js
, I'm getting this:使用
node simple-test.js
运行它,我得到了这个:
delayWithCancellation CancellablePromise::constructor CancellablePromise::constructor successfully delayed.
Why are there two invocations of CancellablePromise::constructor
?为什么有两次 CancelablePromise
CancellablePromise::constructor
调用?
I tried setting breakpoints with VSCode.我尝试使用 VSCode 设置断点。 The stack trace for the second hit shows it's called from
runMicrotasks
, which itself is called from _tickCallback
somewhere inside Node.第二次命中的堆栈跟踪显示它是从
runMicrotasks
,它本身是从_tickCallback
某处的_tickCallback
调用的。
Updated , Google now have "await under the hood" blog post which is a good read to understand this behavior and some other async/await implementation specifics in V8.更新后,谷歌现在有“引擎盖下的等待”博客文章,这是理解这种行为和 V8 中其他一些异步/等待实现细节的好读物。
Updated , as I keep coming back to this, adding static get [Symbol.species]() { return Promise; }
更新,因为我不断回到这个,添加
static get [Symbol.species]() { return Promise; }
static get [Symbol.species]() { return Promise; }
to the CancellablePromise
class solves the problem . static get [Symbol.species]() { return Promise; }
到CancellablePromise
类就解决了这个问题。
First Update:第一次更新:
I first thought .catch( callback)
after 'main' would return a new, pending promise of the extended Promise class, but this is incorrect - calling an async function returns a Promise
promise.我首先想到 'main' 之后的
.catch( callback)
会返回一个新的、未决的扩展 Promise 类的承诺,但这是不正确的 - 调用异步函数会返回一个Promise
承诺。
Cutting the code down further, to only produce a pending promise:进一步削减代码,只产生一个挂起的承诺:
class CancellablePromise extends Promise { constructor(executor) { console.log("CancellablePromise::constructor"); super(executor); } } async function test() { await new CancellablePromise( ()=>null); } test();
shows the extended constructor being called twice in Firefox, Chrome and Node.显示扩展构造函数在 Firefox、Chrome 和 Node.js 中被调用两次。
Now await
calls Promise.resolve
on its operand.现在
await
在其操作数上调用Promise.resolve
。 (Edit: or it probably did in early JS engine's versions of async/await not strictly implemented to standard) (编辑:或者它可能在早期 JS 引擎的 async/await 版本中没有严格按照标准实现)
If the operand is a promise whose constructor is Promise, Promise.resolve
returns the operand unchanged.如果操作数是一个 promise,其构造函数是 Promise,则
Promise.resolve
返回操作数不变。
If the operand is a thenable whose constructor is not Promise
, Promise.resolve
calls the operand's then method with both onfulfilled and onRejected handlers so as to be notified of the operand's settled state.如果操作数是一个 thenable ,其构造函数不是
Promise
, Promise.resolve
使用 onfulfilled 和 onRejected 处理程序调用操作数的 then 方法,以便收到操作数已解决状态的通知。 The promise created and returned by this call to then
is of the extended class, and accounts for the second call to CancellablePromise.prototype.constructor.对
then
调用创建和返回的承诺属于扩展类,并解释了对 CancellablePromise.prototype.constructor 的第二次调用。
new CancellablePromise().constructor
is CancellablePromise
new CancellablePromise().constructor
是CancellablePromise
class CancellablePromise extends Promise { constructor(executor) { super(executor); } } console.log ( new CancellablePromise( ()=>null).constructor.name);
CancellablePromise.prototype.constructor
to Promise
for testing purposes causes only one call to CancellablePromise
(because await
is fooled into returning its operand) :CancellablePromise.prototype.constructor
到Promise
用于测试目的只引起一个呼叫CancellablePromise
(因为await
被愚弄,返回其操作数): class CancellablePromise extends Promise { constructor(executor) { console.log("CancellablePromise::constructor"); super(executor); } } CancellablePromise.prototype.constructor = Promise; // TESTING ONLY async function test() { await new CancellablePromise( ()=>null); } test();
Conforming Implementations符合要求的实现
Per the await
specification根据
await
规范
await
creates an anonymous, intermediate Promise promise with onFulilled and onRejected handlers to either resume execution after the await
operator or throw an error from it, depending on which settled state the intermediate promise achieves. await
创建一个匿名的中间Promise承诺,带有 onFulilled 和 onRejected 处理程序,以在await
运算符之后恢复执行或从中抛出错误,具体取决于中间承诺实现的稳定状态。
It ( await
) also calls then
on the operand promise to fulfill or reject the intermediate promise.它(
await
)还呼吁then
在操作承诺履行或拒绝中间承诺。 This particular then
call returns a promise of class operandPromise.constructor
.这个特殊的
then
调用返回一个类操作数operandPromise.constructor
。 Although the then
returned promise is never used, logging within an extended class constructor reveals the call.尽管从不使用
then
返回的承诺,但在扩展类构造函数中记录会显示调用。
If the constructor
value of an extended promise is changed back to Promise
for experimental purposes, the above then
call will silently return a Promise class promise.如果出于实验目的将扩展承诺的
constructor
值改回Promise
,则上述then
调用将默默地返回一个 Promise 类承诺。
Appendix: Decyphering the await
specification附录:解密
await
规范
Let asyncContext be the running execution context.
让 asyncContext 成为正在运行的执行上下文。
Let promiseCapability be !
让 promiseCapability 成为可能! NewPromiseCapability(%Promise%).
NewPromiseCapability(%Promise%)。
Creates a new jQuery-like deferred object with promise
, resolve
and reject
properties, calling it a "PromiseCapability Record" instead.使用
promise
、 resolve
和reject
属性创建一个新的类似 jQuery 的延迟对象,而是将其称为“PromiseCapability Record”。 The deferred's promise
object is of the (global) base Promise constructor class. deferred 的
promise
对象属于(全局)基础Promise构造函数类。
- Perform !
履行 ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
调用(promiseCapability.[[Resolve]], undefined, « promise »)。
Resolve the deferred promise with the right operand of await
.使用
await
的正确操作数解析延迟的承诺。 The resolution process either calls the then
method of the operand if it is a "thenable", or fulfills the deferred promise if the operand is some other, non-promise, value.如果操作数是“thenable”,解析过程要么调用该操作数的
then
方法,要么在操作数是其他非承诺值时实现延迟承诺。
Let stepsFulfilled be the algorithm steps defined in Await Fulfilled Functions.
让 stepsFulfilled 成为 Await Fulfilled Functions 中定义的算法步骤。
Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, « [[AsyncContext]] »).
让 onFulfilled 成为 CreateBuiltinFunction(stepsFulfilled, « [[AsyncContext]] »)。
Set onFulfilled.[[AsyncContext]] to asyncContext.
将 onFulfilled.[[AsyncContext]] 设置为 asyncContext。
Create an onfulfilled handler to resume the await
operation, inside the async
function it was called in, by returning the fulfilled value of the operand passed as argument to the handler.通过返回作为参数传递给处理程序的操作数的已完成值,在调用它的
async
函数内创建一个 onfulfilled 处理程序以恢复await
操作。
Let stepsRejected be the algorithm steps defined in Await Rejected Functions.
让stepsRejected 成为Await Rejected Functions 中定义的算法步骤。
Let onRejected be CreateBuiltinFunction(stepsRejected, « [[AsyncContext]] »).
让 onRejected 成为 CreateBuiltinFunction(stepsRejected, « [[AsyncContext]] »)。
Set onRejected.[[AsyncContext]] to asyncContext.
将 onRejected.[[AsyncContext]] 设置为 asyncContext。
Create an onrejected handler to resume the await
operation, inside the async
function it was called in, by throwing a promise rejection reason passed to the handler as its argument.创建一个 onrejected 处理程序来恢复
await
操作,在它被调用的async
函数内,通过抛出作为参数传递给处理程序的承诺拒绝原因。
- Perform !
履行 ! PerformPromiseThen(promiseCapability.[[Promise]], onFulfilled, onRejected).
PerformPromiseThen(promiseCapability.[[Promise]], onFulfilled, onRejected).
Call then
on the deferred promise with these two handlers so that await
can respond to its operand being settled.使用这两个处理程序调用
then
延迟承诺,以便await
可以响应其正在解决的操作数。
This call using three parameters is an optimisation that effectively means then
has been called internally and won't be creating or returning a promise from the call.这个使用三个参数的调用是一种优化,有效地意味着
then
已在内部调用,并且不会从调用中创建或返回承诺。 Hence settlement of the deferred will dispatch calling one of its settlement handlers to the promise job queue for execution, but has no additional side effects.因此,延迟的结算将调用其结算处理程序之一到承诺作业队列以供执行,但没有额外的副作用。
Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
从执行上下文堆栈中移除 asyncContext 并将执行上下文堆栈顶部的执行上下文恢复为运行执行上下文。
Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion completion, the following steps of the algorithm that invoked Await will be performed, with completion available.
设置 asyncContext 的代码评估状态,这样当评估通过 Completion 完成恢复时,将执行调用 Await 的算法的以下步骤,完成可用。
Store where to resume after a successful await
and return to the event loop or micro task queue manager.存储成功
await
后恢复的位置并返回到事件循环或微任务队列管理器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.