Promise知识的一些边角料

javascript/jquery

浏览数:205

2019-10-2

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 对象
}

接下来就是我想说的关键部分,也就是resolvereject。平常写的代码可能都是下面这几种:

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回调函数。

接下来可以自己尝试以下几种情况:

  1. 将对象o上的resolve()改成reject(),观察执行then方法的结果
  2. 在o的then里同时调用reject()resolve(),并且调回先后位置。观察执行then后的结果。

总结一下,不论是Promise.resolve()还是构造函数中执行resolve(),传入的如果既不是Promise对象,也不是thenable对象,则返回的是一个完成状态的Promise对象。如果传入的是一个Promise对象,返回的是就是该对象。如果传入的是一个thenable对象,则返回的是作为Promise展开的thenable对象。

那说回开头提到的,resolve标记的之所以是一种完成状态,而不是成功,就在于如果传入的参数为Promise对象或者thenable对象时,如果这两者内部标记的状态为reject,即便外面调用的是resolve,但执行的却是errorCallback。因此平时说Promise的其中一种状态是成功的说法就是错误的。

作者:Aria