Javascript 异步编程-手写Promise

151 阅读9分钟

概述

平常使用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 })
    })
}

参考