扩展Javascript承诺并在构造函数中解析或拒绝它

gum*_*ins 7 javascript inheritance es6-promise es6-class

我想使用ES6语法扩展本机Javascript Promise类,并能够在子类构造函数中调用一些异步函数。基于异步功能的结果,承诺必须被拒绝或解决。

但是,then调用函数时会发生两个奇怪的事情:

  1. 子类构造函数执行两次
  2. 引发“未捕获的TypeError:承诺解决或拒绝函数不可调用”错误

    class MyPromise extends Promise {
        constructor(name) {
            super((resolve, reject) => {
                setTimeout(() => {
                    resolve(1)
                }, 1000)
            })

            this.name = name
        }
    }

    new MyPromise('p1')
        .then(result => {
            console.log('resolved, result: ', result)
        })
        .catch(err => {
            console.error('err: ', err)
        })
Run Code Online (Sandbox Code Playgroud)

小智 8

这篇文章asdru包含正确的答案,但也包含一种应该劝阻的方法(构造函数黑客)。

构造函数 hack 检查构造函数参数是否是一个函数。这不是正确的方法,因为 ECMAScript 设计包含通过Symbol.species.

asdru使用的评论Symbol.species是正确的。请参阅当前ECMAScript 规范中的解释:

Promise 原型方法通常使用其 this 值的构造函数来创建派生对象。但是,子类构造函数可以通过重新定义其 @@species 属性来覆盖默认行为。

finally该规范(间接)在和部分引用了此注释then(查找 的提及SpeciesConstructor)。

通过作为物种构造者返回,可以避免答案分析如此清楚的Promise问题。调用构造函数,但不调用子类构造函数。构造函数仅使用参数调用一次,不需要或不适合进一步的参数检查逻辑。traktorthenPromiseMyPromiseMyPromisename

因此,代码应该简单地是:

class MyPromise extends Promise {
    constructor(name) {
        super((resolve, reject) => {
            setTimeout(() => {
                resolve(1)
            }, 1000)
        })
        this.name = name
    }

    static get [Symbol.species]() {
        return Promise;
    }

    get [Symbol.toStringTag]() {
        return 'MyPromise';
    }
}
Run Code Online (Sandbox Code Playgroud)

少即是多!

一些注意事项:

  • MDN有一个在扩展中使用物种符号的示例Array

  • 最新的浏览器版本(Chrome、FF、Safari、MAC 和 Linux 上的 Edge)可以正确处理此问题,但我没有有关其他浏览器或旧版本的信息。

  • Symbol.toStringTag这是一个非常好的接触,但不是必需的。大多数浏览器使用此符号返回的值来标识控制台中的子类 Promise,但是请注意,FF 不会 - 这很容易造成混淆。然而,在所有浏览器中,new MyPromise('mine').toString()都会产生"[object MyPromise]".

  • 如果您使用 Typescript 进行创作,所有这些也都不成问题。

  • 正如所noseratio指出的,扩展 Promise 的主要用例是包装支持中止或取消逻辑(FileReader、fetch 等)的(传统)API。


tra*_*r53 7

推理很简单,但不一定很明显。

  • .then() 回报承诺
  • 如果then在Promise的子类上调用if ,则返回的Promise是该子类的实例,而不是Promise本身。
  • then返回的承诺是通过调用子类的构造,并通过它记录了该值的内部执行程序功能构建resolve,并reject传递给它供以后使用的参数。
  • “稍后使用”涵盖了then在监视onfulfilledonrejected处理程序的执行情况(稍后)以查看它们是否返回值(解析then返回的承诺)或引发错误(拒绝承诺)时解析或拒绝异步返回的承诺。

简而言之,在then内部获取并记录对它们返回的promise resolvereject函数的引用。


所以关于这个问题,

new MyPromise( 'p1')
Run Code Online (Sandbox Code Playgroud)

工作正常,并且是对子类构造函数的第一个调用。

.then( someFunction)
Run Code Online (Sandbox Code Playgroud)

将记录记录someFunction在进行的then呼叫列表中new MyPromisethen可以多次调用),并尝试通过调用来创建返回承诺

new MyPromise( (resolve, reject) => ... /* store resolve reject references */
Run Code Online (Sandbox Code Playgroud)

这是从then代码对子类构造函数的第二次调用。构造函数应该(并且确实)同步返回。

从创建要返回的保证返回时,该.then方法进行完整性检查,以查看其以后使用的resolveand reject函数是否实际上是函数。它们应该已经与调用中提供的回调一起存储在列表中then

如果MyPromise不是这样的话。甚至没有调用传递then给的执行程序MyPromise。因此,then方法代码将引发类型错误“无法解决Promise resolve或reject函数”-它无法解析或拒绝应返回的诺言。

创建Promise的子类时,子类构造函数必须将执行程序函数作为其第一个参数,并使用实际resolvereject函数参数调用该执行程序。这是then方法代码内部需要的。

做一些复杂的事情MyPromise,也许检查第一个参数以查看它是否是一个函数,然后将其称为执行程序,可能是可行的,但不在此答案范围内!对于所示的代码,编写工厂/库函数可能更简单:


; TLDR

Promise的类扩展不是扩展。如果是这样,则需要实现Promise接口并将执行程序函数作为第一个参数。你可以使用一个工厂函数返回一个承诺,其异步解决(如上),或黑客张贴的代码

MyPromise.prototype.constructor = Promise
Run Code Online (Sandbox Code Playgroud)

这会导致.then返回常规的Promise对象。黑客本身驳斥了正在进行类扩展的想法。


asd*_*dru 6

我发现延长承诺的最佳方式是

class MyPromise extends Promise {
    constructor(name) {
        // needed for MyPromise.race/all ecc
        if(name instanceof Function){
            return super(name)
        }
        super((resolve, reject) => {
            setTimeout(() => {
                resolve(1)
            }, 1000)
        })

        this.name = name
    }

    // you can also use Symbol.species in order to
    // return a Promise for then/catch/finally
    static get [Symbol.species]() {
        return Promise;
    }

    // Promise overrides his Symbol.toStringTag
    get [Symbol.toStringTag]() {
        return 'MyPromise';
    }
}


new MyPromise('p1')
    .then(result => {
        console.log('resolved, result: ', result)
    })
    .catch(err => {
        console.error('err: ', err)
    })
Run Code Online (Sandbox Code Playgroud)