Promise现在已经成为日常开发绕不过去的一个API了,并且也是面试中最喜欢被问到的部分,所以相信大家对它都有一个最基本的认识。所以我并不会再详细介绍这个API的方方面面,而是说一些可能大家日常没有注意到的地方。
Promise作为处理异步函数更好的解决方案,优点在于他有一个初始状态,并且状态发生变化之后就不会再改变,也就是pendding(等待),resolve(完成)和reject(拒绝)。
当执行new Promise(...)之后,返回的是一个Promise对象,虽然这个代码写了不知道多少遍,但是我并没有想过一个问题:Promise对象它有什么特点?怎么才能算一个Promise对象?
查了一些资料之后了解到,Promise对象是具有thenable特征的对象,也就是这个对象上具有then这个属性,不论这个属性是属于对象自身还是存在于原型链的某一处。所以总结一下就是,thenable对象不一定是Promise对象,但是Promise对象一定具有thenable特征。
可以看下面这段代码:
let p = new Promise((resolve,reject)=>{})
if(p !== null && (typeof p === 'object' || typeof p === 'function') && typeof p.then === 'function'){
// thenable 对象
}else{
// 非 thenable 对象
}
接下来就是我想说的关键部分,也就是resolve和reject。平常写的代码可能都是下面这几种:
let something
let p = new Promise((resolve,reject)=>{
// 第一种
resolve(something)
// 第二种
reject(something)
}).then(resolveCallback,errorCallback)
// 第三种
Promise.resolve(something).then(resolveCallback)
// 第四种
Promise.reject(something).then(errorCallback)
Promise本身代表着一种承诺,而且是指向未来的,所以他就有可能成功有可能失败。reject明确表示的是失败状态,然而resolve的翻译是处理完成,这里隐含的意思是并不是明确表示这个承诺就一定成功。
所以对于上面的代码,当something是一个常量(比如数字),根据执行的方法,resolveCallback或者errorCallback就会被执行。了解过少许Promise的原理就会知道,执行Promise.resolve(),js会将传入的参数转换为Promise对象返回,那如果传去的不是一个常量而是一个新的Promise对象又会如何?
let p1 = Promise.resolve('1')
let p2 = Promise.resolve(p1)
p2.then(res=>{
console.log(res) // 这里返回的是什么呢
})
大家可以试着运行一下,结果是1。如果想不明白原因,可以看下面这部分:
let p1 = Promise.resolve('1')
let p2 = Promise.resolve(p1)
console.log(p1 === p2)
此时的结果是true,也就是说把一个Promise对象作为参数传给Promise.resolve,返回的是依旧是传入的Promise对象。以下的结果也是一样:
let p1 = new Promise((resolve, reject) => {
resolve(1)
})
let p2 = Promise.resolve(p1)
console.log(p1 === p2) // true
但是如果把上面代码中的resolve改成reject,则判断条件就是false。也就是说reject并不具备这个特性。
前面说过,Promise对象是具有thenable特性的对象,那现在传入Promise.resolve里的如果就是一个具备then属性的对象,又会如何呢?
let o = {
then(resolve,reject){
}
}
Promise.resolve(o)
这里什么都不会发生,此时如果打印Promise.resolve(o),会发现控制台显示这是一个处于pendding状态的Promise。
用过Promise的知道,它具备一个then方法,有两个参数,第一个是表示resolve的回调函数,第二个是表示reject的回调函数。所以尝试改一下上面的代码。
let o = {
then(resolve,reject){
resolve(1)
}
}
Promise.resolve(o).then(res=>{
console.log(res) // 1
})
当执行对象o上的then属性里的第一个回调函数,作用就类似于调用构造函数是调用的resolve回调函数。
接下来可以自己尝试以下几种情况:
- 将对象o上的
resolve()改成reject(),观察执行then方法的结果 - 在o的then里同时调用
reject()和resolve(),并且调回先后位置。观察执行then后的结果。
总结一下,不论是Promise.resolve()还是构造函数中执行resolve(),传入的如果既不是Promise对象,也不是thenable对象,则返回的是一个完成状态的Promise对象。如果传入的是一个Promise对象,返回的是就是该对象。如果传入的是一个thenable对象,则返回的是作为Promise展开的thenable对象。
那说回开头提到的,resolve标记的之所以是一种完成状态,而不是成功,就在于如果传入的参数为Promise对象或者thenable对象时,如果这两者内部标记的状态为reject,即便外面调用的是resolve,但执行的却是errorCallback。因此平时说Promise的其中一种状态是成功的说法就是错误的。