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
的其中一种状态是成功的说法就是错误的。