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

扩展Javascript承诺并在构造函数中解决或拒绝它,javascript,inheritance,es6-promise,es6-class,Javascript,Inheritance,Es6 Promise,Es6 Class,我想用ES6语法扩展本机Javascript Promise类,并能够在子类构造函数中调用一些异步函数。基于异步函数结果,必须拒绝或解决承诺 然而,当调用然后函数时,会发生两件奇怪的事情: 子类构造函数执行两次 引发“未捕获类型错误:承诺解析或拒绝函数不可调用”错误 类MyPromise扩展了Promise{ 建造师(姓名){ 超级((解决、拒绝)=>{ 设置超时(()=>{ 决议(1) }, 1000) }) this.name=name } } 新MyPromise('p1') 。然后(结

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

然而,当调用
然后
函数时,会发生两件奇怪的事情:

  • 子类构造函数执行两次
  • 引发“未捕获类型错误:承诺解析或拒绝函数不可调用”错误
  • 类MyPromise扩展了Promise{
    建造师(姓名){
    超级((解决、拒绝)=>{
    设置超时(()=>{
    决议(1)
    }, 1000)
    })
    this.name=name
    }
    }
    新MyPromise('p1')
    。然后(结果=>{
    console.log('已解析,结果:',结果)
    })
    .catch(错误=>{
    console.error('err:',err)
    
    })
    推理很简单,但不一定是不言而喻的

    • .then()
      返回一个承诺
    • 如果对Promise的子类调用了
      ,则返回的Promise是该子类的实例,而不是Promise本身
    • 调用子类构造函数,并向其传递一个内部执行器函数,该函数记录传递给它的
      resolve
      reject
      参数的值,以供以后使用,从而构造
      then
      返回的承诺
    • “稍后使用”包括在监视
      oncompleted
      onrejected
      处理程序(稍后)的执行时异步解析或拒绝
      then
      返回的承诺,以查看它们是否返回值(解析
      then
      返回的承诺)或抛出错误(拒绝承诺)
    简而言之,
    然后
    内部调用获取并记录对它们返回的承诺的
    解析
    拒绝
    函数的引用


    那么关于这个问题,,
    new MyPromise( 'p1')
    
    工作正常,是对子类构造函数的第一次调用

    .then( someFunction)
    
    someFunction
    记录在
    然后
    new MyPromise
    的调用列表中(调用
    然后
    可以多次调用),并尝试通过调用

    new MyPromise( (resolve, reject) => ... /* store resolve reject references */
    
    这是对子类构造函数的第二次调用,来自
    then
    code。构造函数应该(并且确实)同步返回

    创建承诺返回后,
    。然后
    方法进行完整性检查,以查看以后使用的
    解析
    拒绝
    函数是否实际上是函数。它们应该与
    然后
    调用中提供的回调一起存储(在列表中)

    MyPromise
    的情况下,它们不是。通过
    然后
    传递到
    MyPromise
    的执行器甚至没有被调用。因此
    然后
    方法代码抛出一个类型错误“Promise resolve或reject函数不可调用”-它无法解析或拒绝它应该返回的承诺

    创建Promise的子类时,子类构造函数必须将executor函数作为其第一个参数,并使用real
    resolve
    reject
    函数参数调用executor。这是
    然后
    方法代码的内部要求

    MyPromise
    执行一些复杂的操作,可能会检查第一个参数以确定它是否是函数,如果是,则将其作为执行器调用,这可能是可行的,但超出了本答案的范围!对于所示代码,编写工厂/库函数可能更简单:

    函数名显示(名称,延迟=1000,值=1){
    var承诺=新承诺((解决、拒绝)=>{
    设置超时(()=>{
    解析(值)
    },延误)
    }
    );
    promise.name=名称;
    回报承诺;
    }
    namedDelay('p1')
    。然后(结果=>{
    console.log('已完成,结果:',结果)
    })
    .catch(错误=>{
    console.error('err:',err)
    
    })
    我发现最好的方法是

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

    asdru
    的帖子包含了正确的答案,但也包含了一种应该被劝阻的方法(构造函数破解)

    构造函数黑客检查构造函数参数是否是函数。这不是,因为ECMAScript设计包含一种特定的机制,通过
    Symbol.species对承诺进行分类

    asdru
    关于使用
    Symbol.species
    的评论是正确的。请参见当前文档中的说明:

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

    规范(间接地)在
    最后
    然后
    的章节中引用了本注释(查看
    规范构造函数

    通过返回
    Promise
    作为物种构造器,可以避免
    traktor
    的答案如此清晰地分析的问题
    然后
    调用
    Promise
    构造函数,但不调用子类的
    MyPromise
    构造函数。
    MyPromise
    构造函数仅使用
    name
    参数调用一次,不需要或不需要进一步的参数检查逻辑

    因此,代码应该是:

    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';
        }
    }
    
    少就是多

    一些注意事项:

    • 有一个在扩展
      数组
      时使用物种符号的示例

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

    • Symbol.toStringTag
      是一种非常好的触摸方式,但不是必需的。大多数眉毛