如何实现一个promise

作者: 丶chlorine | 来源:发表于2020-04-01 15:21 被阅读0次
    image

    promise 是 ES6 中新增的一种异步解决方案,在日常开发中也经常能看见它的身影,例如原生的 fetch API 就是基于 promise 实现的。那么 promise 有哪些特性,如何实现一个具有 promise/A+ 规范的 promise 呢?

    promise 特性

    首先我们整理一下 promise 的一些基本特性和 API,完整的 promise/A+ 规范可以参考 【翻译】Promises/A+规范

    • 状态机
      • 具有 pending、fulfilled、rejected 三个状态
      • 只能由 pending -> fulfilled 和 pending -> rejected 这两种状态变化,且一经改变之后状态不可再变
      • 成功时必须有一个不可改变的值 value,失败时必须有一个不可改变的拒因 reason
    • 构造函数
      • Promise 接受一个函数作为参数,函数拥有两个参数 fulfill 和 reject
      • fulfill 将 promise 状态从 pending 置为 fulfilled,返回操作的结果
      • reject 将 promise 状态从 pending 置为 rejected,返回产生的错误
    • then 方法
      • 接受两个参数 onFulfilled 和 onRejected,分别表示 promise 成功和失败的回调
      • 返回值会作为参数传递到下一个 then 方法的参数中
    • 异步处理
    • 链式调用
    • 其他 API
      • catch、finally
      • resolve、reject、race、all 等

    实现

    接下来我们逐步实现一个具有 promise/A+ 规范的 promise

    基本实现

    先定义一个常量,表示 promise 的三个状态

    const STATE = {
      PENDING: 'pending',
      FULFILLED: 'fulfilled',
      REJECTED: 'rejected'
    }
    

    然后在 promise 中初始化两个参数 value 和 reason,分别表示状态为 fulfill 和 reject 时的值,接着定义两个函数,函数内部更新状态以及相应的字段值,分别在成功和失败的时候执行,然后将这两个函数传入构造函数的函数参数中,如下:

    class MyPromise {
      constructor(fn) {
        // 初始化
        this.state = STATE.PENDING
        this.value = null
        this.reason = null
    
        // 成功
        const fulfill = (value) => {
          // 只有 state 为 pending 时,才可以更改状态
          if (this.state === STATE.PENDING) {
            this.state = STATE.FULFILLED
            this.value = value
          }
        }
    
        // 失败
        const reject = (reason) => {
          if (this.state === STATE.PENDING) {
            this.state = STATE.REJECTED
            this.reason = reason
          }
        }
        // 执行函数出错时调用 reject
        try {
          fn(fulfill, reject)
        } catch (e) {
          reject(e)
        }
      }
    }
    

    接下来初步实现一个 then 方法,当当前状态是 fulfulled 时,执行成功回调,当前状态为 rejected 时,执行失败回调:

    class MyPromise {
      constructor(fn) {
        //...
      }
    
      then(onFulfilled, onRejected) {
        if (this.state === STATE.FULFILLED) {
          onFulfilled(this.value)
        }
        if (this.state === STATE.REJECTED) {
          onRejected(this.reason)
        }
      }
    }
    
    

    这个时候一个简单的 MyPromise 就实现了,但是此时它还只能处理同步任务,对于异步操作却无能为力

    异步处理

    要想处理异步操作,可以利用队列的特性,将回调函数先缓存起来,等到异步操作的结果返回之后,再去执行相应的回调函数。

    具体实现来看,在 then 方法中增加判断,若为 pending 状态,将传入的函数写入对应的回调函数队列;在初始化 promise 时利用两个数组分别保存成功和失败的回调函数队列,并在 fulfill 和 reject 回调中增加它们。如下:

    class MyPromise {
      constructor(fn) {
        // 初始化
        this.state = STATE.PENDING
        this.value = null
        this.reason = null
        // 保存数组
        this.fulfilledCallbacks = []
        this.rejectedCallbacks = []
        // 成功
        const fulfill = (value) => {
          // 只有 state 为 pending 时,才可以更改状态
          if (this.state === STATE.PENDING) {
            this.state = STATE.FULFILLED
            this.value = value
            this.fulfilledCallbacks.forEach(cb => cb())
          }
        }
    
        // 失败
        const reject = (reason) => {
          if (this.state === STATE.PENDING) {
            this.state = STATE.REJECTED
            this.reason = reason
            this.rejectedCallbacks.forEach(cb => cb())
          }
        }
        // 执行函数出错时调用 reject
        try {
          fn(fulfill, reject)
        } catch (e) {
          reject(e)
        }
      }
    
      then(onFulfilled, onRejected) {
        if (this.state === STATE.FULFILLED) {
          onFulfilled(this.value)
        }
        if (this.state === STATE.REJECTED) {
          onRejected(this.reason)
        }
        // 当 then 是 pending 时,将这两个状态写入数组中
        if (this.state === STATE.PENDING) {
          this.fulfilledCallbacks.push(() => {
            onFulfilled(this.value)
          })
          this.rejectedCallbacks.push(() => {
            onRejected(this.reason)
          })
        }
      }
    }
    

    链式调用

    接下来对 MyPromise 进行进一步改造,使其能够支持链式调用,使用过 jquery 等库应该对于链式调用非常熟悉,它的原理就是调用者返回它本身,在这里的话就是要让 then 方法返回一个 promise 即可,还有一点就是对于返回值的传递:

    class MyPromise {
      constructor(fn) {
        //...
      }
    
      then(onFulfilled, onRejected) {
        return new MyPromise((fulfill, reject) => {
          if (this.state === STATE.FULFILLED) {
            // 将返回值传入下一个 fulfill 中
            fulfill(onFulfilled(this.value))
          }
          if (this.state === STATE.REJECTED) {
            // 将返回值传入下一个 reject 中
            reject(onRejected(this.reason))
          }
          // 当 then 是 pending 时,将这两个状态写入数组中
          if (this.state === STATE.PENDING) {
            this.fulfilledCallbacks.push(() => {
              fulfill(onFulfilled(this.value))
            })
            this.rejectedCallbacks.push(() => {
              reject(onRejected(this.reason))
            })
          }
        })
      }
    }
    

    实现到这一步的 MyPromise 已经可以支持异步操作、链式调用、传递返回值,算是一个简易版的 promise,一般来说面试时需要手写一个 promise 时,到这个程度就足够了,完整实现 promise/A+ 规范在面试这样一个较短的时间内也不太现实。

    到这一步的完整代码可以参考 promise3.js

    promise/A+ 规范

    promise/A+ 规范中规定,onFulfilled/onRejected 返回一个值 x,对 x 需要作以下处理:

    • 如果 x 与 then 方法返回的 promise 相等,抛出一个 TypeError 错误
    • 如果 x 是一个 Promise ,则保持 then 方法返回的 promise 的值与 x 的值一致
    • 如果 x 是对象或函数,则将 x.then 赋值给 then 并调用
      • 如果 then 是一个函数,则将 x 作为作用域 this 调用,并传递两个参数 resolvePromiserejectPromise,如果 resolvePromiserejectPromise 均被调用或者被调用多次,则采用首次调用并忽略剩余调用
      • 如果调用 then 方法出错,则以抛出的错误 e 为拒因拒绝 promise
      • 如果 then 不是函数,则以 x 为参数执行 promise
    • 如果 x 是其他值,则以 x 为参数执行 promise

    接下来对上一步实现的 MyPromise 进行进一步优化,使其符合 promise/A+ 规范:

    class MyPromise {
      constructor(fn) {
        //...
      }
    
      then(onFulfilled, onRejected) {
        const promise2 = new MyPromise((fulfill, reject) => {
          if (this.state === STATE.FULFILLED) {
            try {
              const x = onFulfilled(this.value)
              generatePromise(promise2, x, fulfill, reject)
            } catch (e) {
              reject(e)
            }
          }
          if (this.state === STATE.REJECTED) {
            try {
              const x = onRejected(this.reason)
              generatePromise(promise2, x, fulfill, reject)
            } catch (e) {
              reject(e)
            }
          }
          // 当 then 是 pending 时,将这两个状态写入数组中
          if (this.state === STATE.PENDING) {
            this.fulfilledCallbacks.push(() => {
              try {
                const x = onFulfilled(this.value)
                generatePromise(promise2, x, fulfill, reject)
              } catch(e) {
                reject(e)
              }
            })
            this.rejectedCallbacks.push(() => {
              try {
                const x = onRejected(this.reason)
                generatePromise(promise2, x, fulfill, reject)
              } catch (e) {
                reject(e)
              }
            })
          }
        })
        return promise2
      }
    }
    

    这里将处理返回值 x 的行为封装成为了一个函数 generatePromise,实现如下:

    const generatePromise = (promise2, x, fulfill, reject) => {
      if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise'))
      }
      // 如果 x 是 promise,调用它的 then 方法继续遍历
      if (x instanceof MyPromise) {
        x.then((value) => {
          generatePromise(promise2, value, fulfill, reject)
        }, (e) => {
          reject(e)
        })
      } else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
        // 防止重复调用,成功和失败只能调用一次
        let called;
        // 如果 x 是对象或函数
        try {
          const then = x.then
          if (typeof then === 'function') {
            then.call(x, (y) => {
              if (called) return;
              called = true;
              // 说明 y是 promise,继续遍历
              generatePromise(promise2, y, fulfill, reject)
            }, (r) => {
              if (called) return;
              called = true;
              reject(r)
            })
          } else {
            fulfill(x)
          }
        } catch(e) {
          if (called) return
          called = true
          reject(e)
        }
      } else {
        fulfill(x)
      }
    }
    

    promise/A+ 规范中还规定,对于 promise2 = promise1.then(onFulfilled, onRejected)

    • onFulfilled/onRejected 必须异步调用,不能同步
    • 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
    • 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的拒因

    对于 then 方法做最后的完善,增加 setTimeout 模拟异步调用,增加对于 onFulfilled 和 onRejected 方法的判断:

    class MyPromise {
      constructor(fn) {
        //...
      }
    
      then(onFulfilled, onRejected) {
        // 处理 onFulfilled 和 onRejected
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e }
        const promise2 = new MyPromise((fulfill, reject) => {
          // setTimeout 宏任务,确保onFulfilled 和 onRejected 异步执行
          if (this.state === STATE.FULFILLED) {
            setTimeout(() => {
              try {
                const x = onFulfilled(this.value)
                generatePromise(promise2, x, fulfill, reject)
              } catch (e) {
                reject(e)
              }
            }, 0)
          }
          if (this.state === STATE.REJECTED) {
            setTimeout(() => {
              try {
                const x = onRejected(this.reason)
                generatePromise(promise2, x, fulfill, reject)
              } catch (e) {
                reject(e)
              }
            }, 0)
          }
          // 当 then 是 pending 时,将这两个状态写入数组中
          if (this.state === STATE.PENDING) {
            this.fulfilledCallbacks.push(() => {
              setTimeout(() => {
                try {
                  const x = onFulfilled(this.value)
                  generatePromise(promise2, x, fulfill, reject)
                } catch(e) {
                  reject(e)
                }
              }, 0)
            })
            this.rejectedCallbacks.push(() => {
              setTimeout(() => {
                try {
                  const x = onRejected(this.reason)
                  generatePromise(promise2, x, fulfill, reject)
                } catch (e) {
                  reject(e)
                }
              }, 0)
            })
          }
        })
        return promise2
      }
    }
    

    实现 promise/A+ 规范的 promise 完整代码可以参考 promise4.js

    如何知道你实现的 promise 是否遵循 promise/A+ 规范呢?可以利用 promises-aplus-tests 这样一个 npm 包来进行相应测试

    其他 API

    这里对其他常用的 promise API 进行了实现

    catch、finally

    class MyPromise {
      constructor(fn) {
        //...
      }
      then(onFulfilled, onRejected) {
        //...
      }
      catch(onRejected) {
        return this.then(null, onRejected)
      }
      finally(callback) {
        return this.then(callback, callback)
      }
    }
    

    Promise.resolve

    返回一个 resolved 状态的 Promise 对象

    MyPromise.resolve = (value) => {
      // 传入 promise 类型直接返回
      if (value instanceof MyPromise) return value
      // 传入 thenable 对象时,立即执行 then 方法
      if (value !== null && typeof value === 'object') {
        const then = value.then
        if (then && typeof then === 'function') return new MyPromise(value.then)
      }
      return new MyPromise((resolve) => {
        resolve(value)
      })
    }
    

    Promise.reject

    返回一个 rejected 状态的 Promise 对象

    MyPromise.reject = (reason) => {
      // 传入 promise 类型直接返回
      if (reason instanceof MyPromise) return reason
      return new MyPromise((resolve, reject) => {
        reject(reason)
      })
    }
    

    Promise.race

    返回一个 promise,一旦迭代器中的某个 promise 状态改变,返回的 promise 状态随之改变

    MyPromise.race = (promises) => {
      return new MyPromise((resolve, reject) => {
        // promises 可以不是数组,但必须存在 Iterator 接口,因此采用 for...of 遍历
        for(let promise of promises) {
          // 如果当前值不是 Promise,通过 resolve 方法转为 promise
          if (promise instanceof MyPromise) {
            promise.then(resolve, reject)
          } else {
            MyPromise.resolve(promise).then(resolve, reject)
          }
        }
      })
    }
    

    Promise.all

    返回一个 promise,只有迭代器中的所有的 promise 均变为 fulfilled,返回的 promise 才变为 fulfilled,迭代器中出现一个 rejected,返回的 promise 变为 rejected

    MyPromise.all = (promises) => {
      return new MyPromise((resolve, reject) => {
        const arr = []
        // 已返回数
        let count = 0
        // 当前索引
        let index = 0
        // promises 可以不是数组,但必须存在 Iterator 接口,因此采用 for...of 遍历
        for(let promise of promises) {
          // 如果当前值不是 Promise,通过 resolve 方法转为 promise
          if (!(promise instanceof MyPromise)) {
            promise = MyPromise.resolve(promise)
          }
          // 使用闭包保证异步返回数组顺序
          ((i) => {
            promise.then((value) => {
              arr[i] = value
              count += 1
              if (count === promises.length || count === promises.size) {
                resolve(arr)
              }
            }, reject)
          })(index)
          // index 递增
          index += 1
        }
      })
    }
    

    Promise.allSettled

    只有等到迭代器中所有的 promise 都返回,才会返回一个 fulfilled 状态的 promise,并且返回的 promise 状态总是 fulfilled,不会返回 rejected 状态

    MyPromise.allSettled = (promises) => {
      return new MyPromise((resolve, reject) => {
        const arr = []
        // 已返回数
        let count = 0
        // 当前索引
        let index = 0
        // promises 可以不是数组,但必须存在 Iterator 接口,因此采用 for...of 遍历
        for(let promise of promises) {
          // 如果当前值不是 Promise,通过 resolve 方法转为 promise
          if (!(promise instanceof MyPromise)) {
            promise = MyPromise.resolve(promise)
          }
          // 使用闭包保证异步返回数组顺序
          ((i) => {
            promise.then((value) => {
              arr[i] = value
              count += 1
              if (count === promises.length || count === promises.size) {
                resolve(arr)
              }
            }, (err) => {
              arr[i] = err
              count += 1
              if (count === promises.length || count === promises.size) {
                resolve(arr)
              }
            })
          })(index)
          // index 递增
          index += 1
        }
      })
    }
    

    本文如有错误,欢迎批评指正~

    参考

    相关文章

      网友评论

        本文标题:如何实现一个promise

        本文链接:https://www.haomeiwen.com/subject/tykauhtx.html