用了Promise整整7年,直到现在将Promise的链式调用进行一步步拆解,我才终于发现了Promise设计的精妙之处,不知道我这7年是怎么过来的?生活从来不缺少美,缺少的是一双能发现美的眼睛。
Promise的详细介绍
promise是一个代理,它代表一个在promise被创建的时还不一定已知的值。它常常跟异步联系在一起,以异步完成后得到对应的值。
Promise有三种状态,分别是:
- pending(待定):初始状态,既没有兑现,也没有拒绝
- fulfilled(已兑现):意味着操作成功完成
- rejected(已拒绝):意味着操作已失败
Promise的状态变化是单向的,也就是只能从初始状态变成已兑现或者已拒绝,不能反过来。
Promise构造函数
Promise的构造函数接收一个参数executor,该参数是一个函数,该函数接收两个参数:
- resolveFunc 兑现时调用
- rejectFunc 拒绝时调用
executor函数需要用户自己去实现,该函数在Promise构造函数调用的时候会执行一次,用户可以在函数内部调用异步的方法,比如:
javascript
new Promise((resolve, reject)=>{
let res = 'success'
setTimeout(()=>{
resolve(res)
// 或者
// reject('发生错误')
}, 2000)
})
Promise的实例方法
Promise.prototype.then()
then实例方法用于给用户注册已完成状态的回调函数。如果注册的时候,promise对象已经是已完成状态,则会将回调函数微任务队列里进行调用;如果注册的时候,promise兑现还是初始状态,则会在初始状态转变为已完成状态是调用回调函数。
接收的参数:
- onFulfilled:已兑现状态的回调函数
- onRejected:已拒绝状态的回调函数
返回的值:
- 一个新的Promise对象
由于调用then实例方法,每次都会返回一个新的Promise对象,因此就可以实现链式调用了。但是链式调用有个地方让人比较困惑,就是回调函数的返回值,会对then函数的返回的新Promise对象有影响,我们来分析下这个影响,让我们加深对链式调用的理解,从而更好地使用它。
回调函数onFulfilled和onRejected只会调用其中的一个,他们的返回值对then返回的Promise对象的影响是一样的,这里我们已onFulfilled为例来看。
-
onFulfilled回调函数的返回值是一个promise对象,则then返回的promise对象会随着onFulfilled回调函数返回的promise的状态的改变。
javascriptlet promise0Resolve = null let promise0 = new Promise((resolve)=>{ promise0Resolve = resolve }) let promise1 = new Promise((resolve, reject)=>{ resolve(1) }) let promise2 = promise1.then((val)=>{ console.log('promise1.then, val:',val) return promise0 }) promise2.then((val)=>{ console.log('promise2.then, val:', val) }) console.log('before promise0 resolve') promise0Resolve(0) console.log('after promise0 resolve')
控制台的输出为:
js"before promise0 resolve" "after promise0 resolve" "promise1.then, val:" 1 "promise2.then, val:" 0
我们可以看出,promise1.then的返回值是promise2,而promise1.then的onFulfilled回调函数的返回值是 promise1,promise2.then的onFulfilled接收的参数其实就是promise1的resolve传入的值。
-
onFulfilled回调函数没有返回值(返回undefined)或者返回非promise对象,则then返回的promise对象会立即调用resolve,并传入onFulfilled回调函数返回的值。
javascriptlet promise1 = new Promise((resolve, reject)=>{ resolve(1) }) let promise2 = promise1.then((val)=>{ console.log('promise1.then, val:',val) return 2 }) promise2.then((val)=>{ console.log('promise2.then, val:', val) })
控制台输出为:
arduino"promise1.then, val:" 1 "promise2.then, val:" 2
可以看出,promise1.then的返回值是promise2,而promise2.then的onFulfilled接收的参数,恰好是promise1.then的onFulfilled回调函数的返回值。如果promise1.then的onFulfilled回调函数没有返回值或者返回undefined,则promise2.then的onFullfiled函数接收的参数也会是undefined。
Promise.prototype.catch()
catch方法其实跟then很像(实际内部也是调用this.then(null, onRejected)来实现的),唯一的区别就是返回的promise的状态不一样,在已拒绝的状态下会执行onRejected。我们来看个例子。
javascript
let promise1 = new Promise((resolve, reject)=>{
resolve(1)
})
let promise2 = promise1.then((val)=>{
console.log('promise1.then, val:',val)
throw new Error('promise1.then throw error')
})
promise2.catch((val)=>{
console.log('promise2.catch, val:', val)
})
//上面的catch等价于:
promise2.then(null, (val)=>{
console.log('promise2.then onRejected, val:', val)
})
控制台输出为:
vbnet
"promise1.then, val:" 1
"promise2.catch, val:" Error: promise1.then throw error
"promise2.then onRejected, val:" Error: promise1.then throw error
结合promise.then和promise.catch,我们来看一个很容易让人遗憾的例子:
javascript
let promise1 = new Promise((resolve, reject)=>{
resolve(1)
})
let promise2 = promise1.then((val)=>{
console.log('promise1.then, val:',val)
throw new Error('promise1.then throw error')
})
let promise3 = promise2.then((val)=>{
console.log('promise2.then, val:', val)
return 2
})
promise3.catch(val=>{
console.log('promise3.catch, val:', val)
})
控制台输出:
vbnet
"promise1.then, val:" 1
"promise3.catch, val:" Error: promise1.then throw error
我们看到,promise3是promise2.then返回的Promise对象,promise2.then的onFulfilled函数并没有被执行,而promise3的catch的回调函数却执行了,这里看着很神奇,让链式调用可以跳过中间的then,然后直接找到最近的一个catch来捕获错误。这就是Promise设计的精妙之处。
我们来看看上面的场景是怎么实现的。在promise2.then里面,我们只注册了onFulfilled回调函数,没有注册onRejected回调函数,这时候promise2.then返回的promise会立即调用reject,并将错误信息传递进来,也就是promise3返回的时候其实已经是已拒绝的状态。所以它会调用promise3.catch注册进来的onRejected函数。
下来我们再来看一种场景:
javascript
let promise1 = new Promise((resolve, reject)=>{
resolve(1)
})
let promise2 = promise1.then((val)=>{
console.log('promise1.then, val:',val)
throw new Error('promise1.then throw error')
})
let promise3 = promise2.then(null, (val)=>{
console.log('promise2.then onRejected, val:', val)
return 2
})
promise3.catch(val=>{
console.log('promise3.catch, val:', val)
})
控制台输出:
vbnet
"promise1.then, val:" 1
"promise2.then onRejected, val:" Error: promise1.then throw error
我们将promise2.then的第一个参数onFulfilled传入null,而第二个参数onRejected则传入一个回调函数,我们发现这个回调函数被执行了,而promise3.catch中的回调函数却没有执行。其原因是传入promise2.then传入onRejected后,promise3会根据onRejected返回的值来操作同步其状态,也就是promise3不在是一开始就是已拒绝状态,因此promise3.catch的回调函数并不会执行。但如果我们改成promise3.then(val=> console.log('promise3.then, val:', val))
,我们发现就会有输出了:
vbnet
"promise1.then, val:" 1
"promise2.then onRejected, val:" Error: promise1.then throw error
"promise3.then, val:" 2
Promise.prototype.finally()
finally方法的回调函数,无论是已兑现状态或者已拒绝状态都会执行,只不过这个回调函数不接收任何的参数。finally方法也会返回一个Promise对象,该Promise对象的状态和值都会跟前面的then或者catch的值保持一致。
javascript
let promise1 = new Promise((resolve, reject)=>{
resolve(1)
})
let promise2 = promise1.then((val)=>{
console.log('promise1.then, val:',val)
throw new Error('promise1.then throw error')
})
let promise3 = promise2.then(null, (val)=>{
console.log('promise2.then onRejected, val:', val)
return 2
})
let promise4 = promise3.finally((val)=>{
console.log('promise3.finally, val:', val)
})
promise4.then(val=>{
console.log('promise4.then, val:', val)
})
控制台输出:
javascript
"promise1.then, val:" 1
"promise2.then onRejected, val:" Error: promise1.then throw error
"promise3.finally, val:" undefined
"promise4.then, val:" 2