概述
平常使用Promise解决实际问题比较多,今天我们一起来了解Promise在内部是如何实现来链式调用的,Promise的不同API的实现方式,用以加深对Promise的理解。
一个简单的Promise
来看一个例子:
const promise1 = new Promise((resolve, reject) => {
resolve('成功');
reject('失败');
}
//
promise1.then(value => {
console.log('sucess:', value)
}, reason => {
console.log('failed:', reason)
})
运行结果:success: 成功; 如果:调换resolve 和reject 位置:
{
reject('失败');
resolve('成功');
}
运行结果:failed: 失败;
Promise是通过构造函数实例化一个对象,然后通过实例对象上的then方法,来处理返回的结果。同时,promise/A+规范规定了:
promise 是一个拥有 then 方法的对象或函数,其行为符合本规范;一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失败
class MyPromise {
// Promise是一个类,在执行这个类的时候,需要传递一个执行器,执行器会立即执行;
constructor (executor) {}
/**
* Promise 中有三种状态, pending(等待)、fulfilled(成功)、rejected(失败)
**/
status = PENDING
/**
* promise fulfilled 时,需要缓存其值,供内部调用
**/
resolvedValue = null
/**
* promise 状态为REJECTED 时,需要缓存其原因, 供内部调用
**/
rejectedReason = null
/**
* 类型:实例方法-resolve
***/
resolve = value => {}
/**
* 类型:实例方法-reject
***/
reject = reason => {}
/**
* 类型:prototype方法-reject
***/
then(onFulfilled, onRejected) {}
}
module.exports = { MyPromise }
立即执行
当我们实例化Promise时,构造函数会马上调用传入的执行函数executor, 同时将resolve函数和reject函数作为参数传入:
constructor (executor) {
executor(this.resolve, this.reject)
}
由于执行器函数中可能会存在异常,所以,需要捕获异常:
constructor (executor) {
try {
executor(this.resolve, this.reject)
} catch(e) {
this.reject(e)
}
}
不可变
promise/A+规范中规定,当Promise对象已经由等待态(Pending)改变为执行态(Fulfilled)或者拒绝态(Rejected)后,就不能再次更改状态,且终值也不可改变。因此我们在回调函数resolve和reject中判断,只能是pending状态的时候才能更改状态:
// 成功回调
resolve = value => {
if(this.status !== PENDING) return
// constructor init params
this.status = FULFILLED
}
// 失败回调
reject = reason => {
if(this.status !== PENDING) return
// constructor init params
this.status = REJECTED
}
由于MyPromise内部需要用到执行最终值,所以,我们需要缓存成功值或者失败原因:
// 成功回调
resolve = value => {
...
this.resolvedValue = value
}
// 失败回调
reject = reason => {
...
this.rejectedReason = reason
}
then实现
当Promise的状态改变之后,不管成功还是失败,都会触发then回调函数。因此,then的实现也很简单,就是根据状态的不同,来调用不同处理终值的函数。
then(onFulfilled, onRejected) {
if(this.status === FULFILLED) {
onFulfilled(this.resolvedValue)
} else if(this.status === REJECTED) {
onRejected(this.rejectedReason)
}
}
在规范中,onFulfilled和onRejected是可选的,并且对参数有如下说明:
onFulfilled:可选; 如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数
onRejected:可选:如果该参数不是函数,则会在内部被替换为一个 "Thrower" 函数
所以,我们需要兼容以上两种情况:
then(onFulfilled, onRejected) {
onFulfilled = typeof onRejected === 'function' ? onFulfilled : x => x
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
if(this.status === FULFILLED) {
onFulfilled(this.resolvedValue)
} else if(this.status === REJECTED) {
onRejected(this.rejectedReason)
}
}
以上,完成了一个MyPromise的基础功能,来个测试用例:
const promise1 = new Promise((resolve, reject) => {
resolve('成功');// 'sucess: 成功'
reject('失败'); // 'failed: 失败'
setTimeout(resolve, 500, '成功') // No Print
setTimeout(reject, 500, '失败') // No Print
})
promise1.then(value => {
console.log('sucess:', value)
}, reason => {
console.log('failed:', reason)
})
运行结果:同步执行结果正常,异步执行,无任何输出;
分析原因:因为执行器是立即执行的,由于setTimeout中异步执行了resolve/reject方法,导致then方法执行时,MyPromise内部的status为PENDING;当setTimeout执行结束后,then方法中的回调函数已无法触发。
then支持异步
借鉴/引用:传送门-从零开始手写Promise
我们可以参考发布订阅模式,在执行then方法的时候,如果当前还是PENDING状态,就把回调函数寄存到一个数组中,当状态发生改变时,去数组中取出回调函数;因此我们先在Promise中定义一下变量:
class MyPromise {
...
/**
* 同一个promise可以多次调用then方法,当executor中resolve为异步调用时,需要收集promise.then中的onFulfilled
**/
asyncResolveCallbackbs = []
/**
* 同一个promise可以多次调用then方法,当executor中reject为异步调用时,需要收集promise.then中的onRejected
**/
asyncRejectCallbacks = []
...
}
当then执行时,如果还是PENDING状态,我们不是马上去执行回调函数,而是将其存储起来:
then(onFulfilled, onRejected) {
onFulfilled = typeof onRejected === 'function' ? onFulfilled : x => x
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
if(this.status === FULFILLED) {
onFulfilled(this.resolvedValue)
} else if(this.status === REJECTED) {
onRejected(this.rejectedReason)
} else {
this.asyncResolveCallbackbs.push(onFulfilled)
this.asyncRejectCallbacks.push(onRejected)
}
}
存储起来后,当resolve或者reject异步执行的时候就可以来调用了:
resolve = value => {
...
// async resolve
while(this.asyncResolveCallbackbs.length) {
const curAsyncResolveCb = this.asyncResolveCallbackbs.shift()
curAsyncResolveCb(this.resolvedValue)
}
}
reject = reason => {
...
// async reject
while(this.asyncRejectCallbacks.length) {
const curAsyncRejectCb = this.asyncRejectCallbacks.shift()
curAsyncRejectCb(this.rejectedReason)
}
}
运行上面的例子, 运行结果正常。
then支持链式调用
借鉴/引用/详细解释:传送门-从零开始手写Promise
then 方法可以实现链式调用,每一个then方法返回的都是一个新的Promise对象。同时,后一个then方法接收的是前一个then方法的回调函数的返回值;
改写then方法, 不论then进行什么操作,都返回一个新的Promise对象:
then(onFulfilled, onRejected) {
// callback is null
onFulfilled = onFulfilled ? onFulfilled : x => x
onRejected = onRejected ? onRejected : reason => { throw reason }
const newThenPromise = new MyPromise((resolve, reject) => {
// 外层执行作用域为同步任务,执行器为立即执行函数;开启宏任务获取外层作用域的newThenPromise对象
setTimeout(() => {
// 集中捕获then回调中抛出的异常,并传递给下一个then方法
const catchThenException = (callbackFunc, cacheValue) => {
try {
const callbackFuncValue = callbackFunc(cacheValue)
// 链式调用,上一个返回值可能为常规值,也可能为promise对象
callbackFuncValue && resolvePromise(callbackFuncValue, newThenPromise, resolve, reject)
} catch (e) {
reject(e)
}
}
// console.log('____this.status', this.status)
if(this.status === FULFILLED) {
// onFulfilled(this.resolvedValue)
catchThenException(onFulfilled, this.resolvedValue)
} else if(this.status === REJECTED) {
// onRejected(this.rejectedReason)
catchThenException(onRejected, this.rejectedReason)
} else {
// 同步 | 异步
// this.asyncResolveCallbackbs.push(onFulfilled)
// this.asyncRejectCallbacks.push(onRejected)
// 同步 | 异步 | 链式调用 => 收集成功回调
this.asyncResolveCallbackbs.push(() => {
catchThenException(onFulfilled, this.resolvedValue)
})
// 同步 | 异步 | 链式调用 => 收集失败回调
this.asyncRejectCallbacks.push(() => {
catchThenException(onRejected, this.rejectedReason)
})
}
}, 0)
})
return newThenPromise
}
catch 方法实现
catch 方法是一个原型链方法,用于捕获之前函数运行中的reject异常,MDN中对catch的描述:
catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。
所以,catch 方法是一个then方法的封装:
catch(onRejected) {
return this.then(undefined, onRejected)
}
Promise.all 方法实现
all 方法是一个静态方法,它返回一个Promise, MDN中对all的描述:
- 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise。
- 如果传入的参数不包含任何 promise,则返回一个异步完成。
- 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)
- 如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。
static all(array) {
const resolveResult = [] // all方法返回的结果集
return new MyPromise((resolve, reject) => {
const setResolveResult = (index, value) => {
resolveResult[index] = value
// 异步函数会存在空值的情况
const effectiveResult = resolveResult.filter(effctive => {
if(effctive) return effctive
})
// 等待所有参入参数都有确定的返回结果
effectiveResult.length === array.length && resolve(resolveResult)
}
for(let i = 0; i<array.length; i++) {
const curArgument = array[i]
// 如果是Promise,则执行当前promise拿到返回结果
if(curArgument instanceof MyPromise) {
curArgument.then(value => setResolveResult(i, value), reject)
} else {
setResolveResult(i, curArgument)
}
}
})
}
Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。
Promise.allSettled 方法实现
allSettled 方法是一个静态方法,它返回一个Promise, MDN中对allSettled的描述:
该Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
static all(array) {
const resolveResult = []
return new MyPromise((resolve, reject) => {
const setEachResult = (index, value) => {
resolveResult[index] = value
// 异步函数会存在空值的情况
const effectiveResult = resolveResult.filter(effctive => {
if(effctive) return effctive
})
// 等待所有参入参数都有确定的返回结果
effectiveResult.length === array.length && resolve(resolveResult)
}
for (let i = 0; i < array.length; i++) {
const curArgument = array[i]
if(curArgument instanceof MyPromise) {
curArgument.then(value => {
setEachResult(i, value)
}, reason => {
setEachResult(i, reason)
})
} else {
setEachResult(i, curArgument)
}
}
})
}
当有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用allSettled。
any方法的实现
any 方法是一个静态方法,它返回一个Promise, MDN中对any的描述:
Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。
static any(array) {
return new MyPromise((resolve, reject) => {
let setEffectvalue = null;
let rejectCount = 0
const effctivePromise = array.filter(cur => {
if(cur instanceof MyPromise) return cur
})
// 传入非Promise 参数,则需特殊处理
if(array.length === 0) {
reject('No Arguments is Effctive')
return
} else if(effctivePromise.length === 0) {
resolve(array)
return
}
for (let i = 0; i < effctivePromise.length; i++) {
// 返回第一个成功的结果,不在乎其它传入promise的执行状态
if(setEffectvalue) break;
effctivePromise[i].then(resolveValue => {
setEffectvalue = resolveValue
resolve(setEffectvalue)
}, reason => {
rejectCount++
// 如果所有Promise全部执行失败,则返回执行异常
rejectCount === effctivePromise.length && reject('AggregateError: No Promise in Promise.any was resolved')
})
}
})
}
这个方法用于返回第一个成功的 promise 。只要有一个 promise 成功此方法就会终止,它不会等待其他的 promise 全部完成。
race 方法的实现
race 方法是一个静态方法,它返回一个Promise, MDN中对race的描述:
race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
如果传的迭代是空的,则返回的 promise 将永远等待。
如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
static race(array) {
let isEffectValue = null // 存在有效最终值则程序不在继续执行
return new Promise((resolve, reject) => {
if(array.length === 0) return
for (let i = 0; i < array.length; i++) {
if(isEffectValue) break
const cur = array[i]
if(cur instanceof MyPromise) {
cur.then(value => {
isEffectValue = value
resolve(isEffectValue)
}, reason => {
isEffectValue = reason
reject(isEffectValue)
})
}
}
})
}
resolve 方法的实现
resolve 方法是一个静态方法,它返回一个Promise, MDN中对race的描述:
静态方法 Promise.resolve返回一个解析过的Promise对象。如果参数本身就是一个Promise对象,则直接返回这个Promise对象。
static resolve(value) {
if(value instanceof MyPromise) return value
return new MyPromise(resolve => resolve(value))
}
finaly方法的实现
finaly方法是一个原型链方法,它返回一个Promise, MDN中对finaly的描述:
finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
语法:
p.finally(onFinally)
p.finally(function() {
// doSomething
})
说明:
- 调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它。
- 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。
- finaly 可以设置返回一个方法,当方法执行结束之后在执行链式调用then方法。
finaly(callback) {
return this.then(value => {
return MyPromise.resolve(callback()).then(() => value)
}, reason => {
return MyPromise.resolve(callback()).then(() => { throw reason })
})
}