扩展Javascript承诺并在构造函数中解决或拒绝它
我想用ES6语法扩展本机Javascript Promise类,并能够在子类构造函数中调用一些异步函数。基于异步函数结果,必须拒绝或解决承诺 然而,当调用扩展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') 。然后(结
然后函数时,会发生两件奇怪的事情:
子类构造函数执行两次
引发“未捕获类型错误:承诺解析或拒绝函数不可调用”错误
类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函数作为其第一个参数,并使用realresolve
和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
是一种非常好的触摸方式,但不是必需的。大多数眉毛