gpt4 book ai didi

javascript - 自定义 Promise 类的构造函数被调用两次(扩展标准 Promise)

转载 作者:行者123 更新时间:2023-11-30 14:18:31 24 4
gpt4 key购买 nike

我在玩Promise Extensions for JavaScript (prex)我想扩展标准Promise class使用 prex.CancellationToken 取消支持, complete code here .
出乎意料的是,我看到了我的自定义类的构造函数 CancellablePromise被调用两次。为了简化事情,我现在剥离了所有取消逻辑,只留下了重现问题所需的最低限度:

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));

使用 node simple-test.js 运行它,我得到这个:
延迟取消
可取消 promise ::构造函数
可取消 promise ::构造函数
成功延迟。

为什么有两次调用 CancellablePromise::constructor ?
我尝试使用 VSCode 设置断点。第二次命中的堆栈跟踪显示它是从 runMicrotasks 调用的。 ,它本身是从 _tickCallback 调用的Node.js 内部的某个地方。
更新 , 谷歌现在有 "await under the hood"这篇博文非常适合理解这种行为以及 V8 中的一些其他异步/等待实现细节。
更新 ,当我不断回到这一点时,添加 static get [Symbol.species]() { return Promise; }CancellablePromise类(class) solves the problem .

最佳答案

第一次更新:

我首先想到.catch( callback)在 'main' 之后将返回扩展 Promise 类的新的未决 promise ,但这是不正确的 - 调用异步函数返回 Promise promise 。

进一步削减代码,只产生一个未决的 promise :

class CancellablePromise extends Promise {
constructor(executor) {
console.log("CancellablePromise::constructor");
super(executor);
}
}

async function test() {
await new CancellablePromise( ()=>null);
}
test();


显示扩展构造函数在 Firefox、Chrome 和 Node 中被调用两次。

现在 await来电 Promise.resolve在它的操作数上。 (编辑:或者它可能在早期 JS 引擎的 async/await 版本中没有严格执行标准)

如果操作数是构造函数为 Promise 的 Promise, Promise.resolve返回操作数不变。

如果操作数是一个thenable,其构造函数不是 Promise , Promise.resolve使用 onfulfilled 和 onRejected 处理程序调用操作数的 then 方法,以便通知操作数的已解决状态。此调用创建并返回的 promise then属于扩展类,并解释了对 CancelablePromise.prototype.constructor 的第二次调用。

支持证据
  • new CancellablePromise().constructorCancellablePromise


  • class CancellablePromise extends Promise {
    constructor(executor) {
    super(executor);
    }
    }

    console.log ( new CancellablePromise( ()=>null).constructor.name);


  • 更改 CancellablePromise.prototype.constructorPromise出于测试目的,只导致一次调用 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();



    第二次更新(非常感谢 OP 提供的链接)

    符合规范的实现

    根据 await specification
    await创建一个匿名的中间 promise 使用 onFulilled 和 onRejected 处理程序 promise
    await 之后恢复执行运算符或从中抛出错误,具体取决于中间 promise 达到的稳定状态。

    它 ( await ) 也调用 then在操作数上 promise 履行或拒绝中间 promise 。这个特殊的 then调用返回类 operandPromise.constructor 的 promise .虽然 then从不使用返回的 promise ,在扩展类构造函数中记录会显示调用。

    如果 constructor扩展 promise 的值改回 Promise出于实验目的,以上 then call 将默默地返回一个 Promise 类 promise 。

    附录:解密 await specification

    1. Let asyncContext be the running execution context.

    2. Let promiseCapability be ! NewPromiseCapability(%Promise%).



    使用 promise 创建一个新的类似 jQuery 的延迟对象, resolvereject属性,而是将其称为“PromiseCapability Record”。延期的 promise对象是(全局)基 promise 构造函数类。

    1. Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).


    使用 await 的右操作数解决延迟 promise .解析过程调用 then如果操作数是“thenable”,则操作数的方法,或者如果操作数是其他一些非 promise 值,则满足延迟 promise 。

    1. Let stepsFulfilled be the algorithm steps defined in Await Fulfilled Functions.

    2. Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, « [[AsyncContext]] »).

    3. Set onFulfilled.[[AsyncContext]] to asyncContext.



    创建一个 onfulfilled 处理程序以恢复 await操作,在 async里面通过返回作为参数传递给处理程序的操作数的已实现值来调用它的函数。

    1. Let stepsRejected be the algorithm steps defined in Await Rejected Functions.

    2. Let onRejected be CreateBuiltinFunction(stepsRejected, « [[AsyncContext]] »).

    3. Set onRejected.[[AsyncContext]] to asyncContext.



    创建一个 onrejected 处理程序以恢复 await操作,在 async里面它被调用的函数,通过抛出一个作为参数传递给处理程序的 promise 拒绝原因。

    1. Perform ! PerformPromiseThen(promiseCapability.[[Promise]], onFulfilled, onRejected).


    调用 then关于这两个处理程序的延迟 promise ,以便 await可以响应其操作数被结算。

    这个使用三个参数的调用是一种优化,实际上意味着 then已在内部调用,并且不会从调用中创建或返回 promise 。因此,延迟的结算将调度调用其结算处理程序之一到 promise 作业队列执行,但没有额外的副作用。

    1. 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.

    2. 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.



    存储成功后恢复到哪里 await并返回到事件循环或微任务队列管理器。

    关于javascript - 自定义 Promise 类的构造函数被调用两次(扩展标准 Promise),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53146565/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com