Promise原理

72 阅读13分钟

手写Promise对象

最近准备总结一下ES6的知识,今天来手写一个Promise,在写之前我们先来看看Promise的概念、特点及其用法。

概念

Promise是异步编程的一种解决方案。用来解决之前的回调地狱等的问题。

相对于之前的回调函数和事件更合理也更强大

特点

  • 对象不受外界影响
  • 一旦状态状态改变,就不会在变。

缺点

  • 无法取消,一旦创建就会立即执行
  • 不设置回调函数,抛出的错误不会反应到外部
  • 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

基本用法

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

实现一个Promise

Promise的方法

实例方法:

  • then
  • catch
  • finally

静态方法:

  • resolve
  • reject
  • race
  • all
  • allSettled
  • any

接下来我们根据Promise的定义及它的特点来实现一个Promise

构造函数

首先,Promise是一个构造函数,所以我们要定一个类,添加构造函数,定义resolve reject 接收参数。

// 1. Promise是一个构造函数,所以我们要定一个class类
class Promise {
    // 2. 添加构造函数,通过`new`命令生成对象实例时,自动调用该方法
    constructor (func){
        // 3. 定义`resolve` `reject` 接收参数
        const resolve = ( ) => {}
        const reject = () => {}
        // 4.执行回调函数,因为Promise一旦创建立即执行
        func(resolve, reject)
    }
}

状态及原因

Promise是根据状态机制来实现的,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

const PENDING = 'pending' // 进行中
const FULFILLED = 'fulfilled' // 已成功
const REJECTED = 'rejected' // 以失败

class Promise {
   // 1. 添加状态 (pending / fulfilled / rejected) 初始是pending状态
   state = PENDING
   // 2. 添加原因
   result = undefined
    constructor (func){
        // 3. 调整resolve reject 
        const resolve = (result) => {
            // 4. 状态不可变
            if(this.setate === PENDING){
                this.state = FULFILLED
                this.result = result
            }
        }
        const reject = (result) => {
            if(this.setate === PENDING){
                this.state = REJECTED
                this.result = result
            }
        }
        func(resolve, reject)
    }
}

then方法

Promise 实例的 then()  方法最多接受两个参数:用于 Promise 兑现和拒绝情况的回调函数。它立即返回一个等效的 Promise 对象,允许你链接到其他 Promise 方法,从而实现链式调用

然后,我们来看一下MDN对参数的解释:

image.png

根据then方法的定义和对参数的描述,来总结一下then需要实现的几点:

  1. 参数需要进行判断:onFulfilled如果不是函数就将其包装成一个函数((x) => x)。onRejected如果不是一个函数就将其包装成一个抛出错误的函数((x) => { throw x;}
  2. then方法会返回一个等效的Promsie对象,来实现链式调用,所以需要抛出一个Promise函数。
  3. then方法的回调函数是异步执行的。参考vue2中的写法,因为我们是手写Promise所以排除Promise,运行环境是浏览器排除process.nextTick,所以选用queueMicrotaskMutationObserversetTimeout

所以我们需要写一个将回调函数包成成异步代码的方法runAsynctask

function runAsynctask(callback) {
  // 调用核心api(queueMicrotask  MutationObserver  setTimeout)
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'funciton') {
    const obs = new MutationObserver(callback)
    const div = document.createElement('div')
    obs.observe(div, { childList: true })
    div.innerHTML = 'test'
  } else {
    setTimeout(callback, 0)
  }
}

接下来我们写一个初步的then方法

then(onFulfilled, onRejected) {
    // 1. 参数需要进行判断
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
    // 2. 返回一个Promise(这里的Promise指的是我们class创建的Promise)
    const p2 = new Promise((resolve, reject) => {
      if (this.state === FULFILLED) {
      // 3. then的回调是异步执行的,直接调用上面写的runAsynctask方法
        runAsynctask(() => {
            // 调用成功回调reslove
            reslove(onFulfilled(this.result))
        })
      } else if (this.state === REJECTED) {
        runAsynctask(() => {
            // 调用失败回调reslove
            reject(onRejected(this.result))
        })
      } else if (this.state === PENDING) {
        // 执行then时,状态还没有改变,则把回调存起来,等执行回调的时候再一次执行。
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
                onFulfilled(this.result)
            })
          },
          onRejected: () => {
            runAsynctask(() => {
               onRejected(this.result)
            })
          }
        })
      }
    })
    return p2
  }

接下来我们看一下Promise.then()针对执行函数返回值是如何处理的。主要有以下几种情况:

  1. 重复引用,会报一个错误(chaining cycle deteted for promise #<Promise>)如下图:

image.png

  1. 如果Promise的回调函数中返回的是Promise,那么无论触发的是Promise链中的then方法还是catch方法,新生成的Promise对象的状态都直接取决于回调函数中返回的Promise对象的状态,传进下一个回调函数的值也取决于这个被返回的Promise对象,让我们看下面几个例子:

image.png

image.png

  1. 对象或者函数
  2. 返回值

image.png

把这些情况封装成一个函数就是:

function resolvePromise(p2, x, resolve, reject) {
   // 1. 处理重复引用
  if (x === p2) {
    throw new TypeError('chaining cycle deteted for promise #<Promise>')
  }
  // 2. 处理返回一个Promise
  if (x instanceof Promise) {
    x.then((res) => {
      resolve(res)
    }, (err) => {
      reject(err)
    })
  } else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
  // 3. 对象或者函数
    try {
      var then = x.then;
    } catch (err) {
      return reject(err)
    }
    if(typeof then === 'function') {
      let called = false
      try {
        then.call(x,y => {
          if(called) return;
          called = true;
          resolvePromise(p2, y ,resolve,reject)
        },r => {
          if(called) return;
          called = true;
          reject(r)
        })
      } catch (error) {
        if(called) return;
        called = true;
        reject(error)
      }
    } else {
      resolve(x)
    }
  } else {
     // 4. 处理返回值
    return resolve(x)
  }
}

将上面合并一下:

const PENDING = 'pending' // 进行中
const FULFILLED = 'fulfilled' // 已成功
const REJECTED = 'rejected' // 以失败

class Promise {
   // 添加状态 (pending / fulfilled / rejected)
   state = PENDING
   //  添加原因
   result = undefined
   // 回调函数的队列
   #handlers = []
    constructor (func){
        // 调整resolve reject 
        const resolve = (result) => {
            // 状态不可变
            if(this.setate === PENDING){
                this.state = FULFILLED
                this.result = result
                // 如果在调用resolve时,回调函数里有数据,则循环执行
                this.#handlers.forEach(({ onFulfilled }) => {
                  onFulfilled(result)
                })
            }
        }
        const reject = (result) => {
            if(this.setate === PENDING){
                this.state = REJECTED
                this.result = result
                // 如果在调用reject时,回调函数里有数据,则循环执行
                this.#handlers.forEach(({ onRejected }) => {
                  onRejected(result)
                })
            }
        }
        func(resolve, reject)
    }
}
    //  1. 添加实例方法
    then(onFulfilled, onRejected) {
        // 判断参数是否是函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
        onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
        const p2 = new Promise((resolve, reject) => {
          if (this.state === FULFILLED) {
            runAsynctask(() => {
              // 获取返回值
              try {
                const x = onFulfilled(this.result)
                // 处理重复引用
                // if(x === p2){
                //   throw new TypeError('chaining cycle deteted for promise #<Promise>')
                // }
                // // 判断是否是promise
                // if(x instanceof Promise){
                //     x.then((res) => {
                //       resolve(res)
                //     },(err) => {
                //       reject(err)
                //     })
                // } else {
                //   resolve(x)
                // }
                // 调用函数
                resolvePromise(p2, x, resolve, reject)

              } catch (error) {
                reject(error)
              }

            })
          } else if (this.state === REJECTED) {
            runAsynctask(() => {
              try {
                // 获取返回值
                const x = onRejected(this.result)
                // 处理promise
                resolvePromise(p2, x, resolve, reject)
              } catch (error) {
                reject(error)
              }
            })
          } else if (this.state === PENDING) {
            // 执行then时,状态还没有改变,则把回调存起来
            this.#handlers.push({
              onFulfilled: () => {
                runAsynctask(() => {
                  // 1. 处理异常
                  try {
                    // 2. 获取返回值
                    const x = onFulfilled(this.result)
                    // 3. 调用函数
                    resolvePromise(p2, x, resolve, reject)
                  } catch (error) {
                    reject(error)
                  }
                })
              },
              onRejected: () => {
                runAsynctask(() => {
                  try {
                    const x = onRejected(this.result)
                    resolvePromise(p2, x, resolve, reject)
                  } catch (error) {
                    reject(error)
                  }
                })
              }
            })
          }
        })
        return p2
      }
  }

catch实例方法

Promise 实例的 catch()  方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 的方法。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。

综上所述 catch() 方法如下:

catch(onRejected){
     // 内部调用then
    return this.then(undefined,onRejected)
}

finally实例方法

finally不管Promise对象最后的状态如何,都会执行的操作。finally本质上是then方法的特例。

那就是then方法不管成功还是失败都执行finally传入的回调函数就可以了,如下:

finally(onFinally){
     // 内部调用then
    return this.then(onFinally,onFinally)
}

reslove静态方法

Promsie.reslove()静态方法将给定的值转换为一个特定的Promise,如果该值本身是一个Promise,那么该Promise被返回,如果该值是一个thenable对象,Promise.resolve() 将调用其 then() 方法及其两个回调函数;否则,返回的 Promise 将会以该值兑现。

static reslove(value){
    if (value instanceof Promise) {
      return value
    } else {
      return new Promise((resolve) => {
        resolve(value)
      })
    }
}

reject静态方法

方法也会返回一个新的 Promise 实例,该实例的状态为rejected

static reject(value){
  return new Promise((resolve,reject) => {
    reject(value)
  })
}

race静态方法

将多个Promise实例,包装成一个新的Promise实例,这个返回的 promise 会随着第一个 promise 的敲定而敲定。

static race(promises) {
    // 1. 返回promise
    return new Promise((resolve, reject) => {
      // 2. 判断是否为数组,错误信息 Argument is not iterable
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 3. 等待第一个敲定
      promises.forEach(p => {
        this.resolve(p).then((res) => {
          resolve(res)
        }, (err) => {
          reject(err)
        })
      })
    })
  }

all静态方法

返回一个Promise,等待所有输入的Promise都兑现,并返回一个包含所有兑现值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。

static all(promises) {
    // 返回一个Promise
    return new Promise((resolve, reject) => {
       // 判断是否为数组
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 如果是空数组,则返回一个兑现的空数组
      promises.length === 0 && resolve(promises)
      const result = []
      let count = 0
      promises.forEach((p, index) => {
        this.resolve(p).then((res) => {
           // 根据下标来保持输出的结果与传入的参数一一对应
          result[index] = res
          count++ // 用来判断是否是全部兑现
          count === promises.length && resolve(result)
        }, (err) => {
          // 如果其中一个被拒绝,则直接返回别拒绝的原因
          reject(err)
        })
      })
    })
  }

allSettled静态犯法

Promise.allSettled()  静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。

static allSettled(promises){
    // 1. 返回一个promsie
    return new Promise((resolve, reject) => {
      // 2. 数组判断
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 3. 为空直接敲定
      promises.length === 0 && resolve(promises)
      // 4. 等待全部敲定
      // 4.1 记录结果:数组
      const result = []
      let count = 0 // 使用次数来记录是否全部敲定
      promises.forEach((p,index) => {
        this.resolve(p).then((res) => {
          // 4.2 处理兑现:{state: 'fulfilled',value:1} 
          result[index] = {state: FULFILLED, value:res}//  为保持返回数据和传入顺序一致,使用下标赋值
          count++
          count === promises.length && resolve(result)
        },err=>{
          // 4.2 处理拒绝
          result[index] = { state: REJECTED, reason: err}
          count++
          count === promises.length && resolve(result)

        })
      })
    })
  }

any静态方法

Promise.any()  静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。

  static any (promises){
    // 1. 返回promises,数组判断 错误信息Argument is not iterable
    return new Promise((resolve,reject) => {
      if(!Array.isArray(promises)){
        return reject(new TypeError('Argument is not iterable'))
      }
      // 2. 空数组 直接拒绝
      promises.length === 0 && reject(new AggregateError(promises,'All promises were rejected'))
      // 3. 等待结果
      const errors = []
      let count = 0
      promises.forEach((p,index) => {
        this.resolve(p).then((res) => {
          // 3.1 第一个兑现 
          resolve(res)
        },err => {
          // 3.2 全部拒绝 :All promises were rejected --[1,2,3]
          errors[index] = err
          count++
          promises.length === count && reject(new AggregateError(errors,'All promises were rejected'))
        })
      })
    })

  }

完整代码

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// 1. 定义类
class Promise {
  // 实例属性
  state = PENDING //状态 
  // 原因
  result = undefined
  // 回调函数
  #handlers = []

  // 添加构造函数  new时会执行
  constructor(func) {
    // 2. 接收回调函数func
    // 3. 定义resolve  reject 接收参数
    const resolve = (result) => {
      // pending => fulfilled
      // 记录原因
      if (this.state === PENDING) {
        this.state = FULFILLED
        this.result = result
        // 如果在调用resolve时,回调函数里有数据,则循环执行
        this.#handlers.forEach(({ onFulfilled }) => {
          onFulfilled(result)
        })
      }

    }
    const reject = (result) => {
      // pending => rejected
      // 记录原因
      if (this.state === PENDING) {
        this.state = REJECTED
        this.result = result
        // 如果在调用reject时,回调函数里有数据,则循环执行
        this.#handlers.forEach(({ onRejected }) => {
          onRejected(result)
        })
      }
    }
    try {
      // 4. 执行回调函数,因为Promise一旦创建就会立即执行
      func(resolve, reject)
    } catch (error) {
      reject(error)
    }

  }

  // 添加实例方法
  then(onFulfilled, onRejected) {
    // 判断参数是否是函数

    // 参考  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
    onRejected = typeof onRejected === 'function' ? onRejected : x => { throw x }
    const p2 = new Promise((resolve, reject) => {
      if (this.state === FULFILLED) {
        runAsynctask(() => {
          // 获取返回值
          try {
            const x = onFulfilled(this.result)
            // 处理重复引用
            // if(x === p2){
            //   throw new TypeError('chaining cycle deteted for promise #<Promise>')
            // }
            // // 判断是否是promise
            // if(x instanceof Promise){
            //     x.then((res) => {
            //       resolve(res)
            //     },(err) => {
            //       reject(err)
            //     })
            // } else {
            //   resolve(x)
            // }
            // 调用函数
            resolvePromise(p2, x, resolve, reject)

          } catch (error) {
            reject(error)
          }

        })
      } else if (this.state === REJECTED) {
        runAsynctask(() => {
          try {
            // 获取返回值
            const x = onRejected(this.result)
            // 处理promise
            resolvePromise(p2, x, resolve, reject)
          } catch (error) {
            reject(error)
          }
        })
      } else if (this.state === PENDING) {
        // 执行then时,状态还没有改变,则把回调存起来
        this.#handlers.push({
          onFulfilled: () => {
            runAsynctask(() => {
              // 1. 处理异常
              try {
                // 2. 获取返回值
                const x = onFulfilled(this.result)
                // 3. 调用函数
                resolvePromise(p2, x, resolve, reject)
              } catch (error) {
                reject(error)
              }
            })
          },
          onRejected: () => {
            runAsynctask(() => {
              try {
                const x = onRejected(this.result)
                resolvePromise(p2, x, resolve, reject)
              } catch (error) {
                reject(error)
              }
            })
          }
        })
      }
    })
    return p2
  }
  // catch  是.then(null,rejection) 或者.then(undefined,rejection)的别名,用于指定发生错误时的回调函数。
  /**
   * 用法: 
   *  p.then(res => console.log(res)).catch(err => console.log(err))
   */
  /**
   * 1.内部调用then
   * 2.处理异常
   */
  catch(onRejected) {
    // 内部调用then
    return this.then(undefined, onRejected)
    // this.then()
  }

  /**
   * @name finally
   * @description 不管Promise对象最后的状态如何,都会执行的操作。finally本质上是then方法的特例
   * 
   */
  finally(onFinally) {
    return this.then(onFinally, onFinally)

  }
  /**
   * 是Promise直接返回
   * 转为Promise并返回(fulfilled状态)
   */
  static resolve(value) {
    if (value instanceof Promise) {
      return value
    } else {
      return new Promise((resolve) => {
        resolve(value)
      })
    }
  }

  /**
   * 方法也会返回一个新的 Promise 实例,该实例的状态为rejected
   */

  static reject(value) {
    return new Promise((resolve, reject) => {
      reject(value)
    })
  }


  /**
   * @name race 将多个Promise实例,包装成一个新的Promise实例,这个返回的 promise 会随着第一个 promise 的敲定而敲定
   * @param {*} array 
   * 
   * 1. 返回promise
   * 2. 判断是否为数组,错误信息 Argument is not iterable
   * 3. 等待第一个敲定
   */

  static race(promises) {
    // 1. 返回promise
    return new Promise((resolve, reject) => {
      // 2. 判断是否为数组,错误信息 Argument is not iterable
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 3. 等待第一个敲定
      promises.forEach(p => {
        // 
        this.resolve(p).then((res) => {
          resolve(res)
        }, (err) => {
          reject(err)
        })
      })

    })
  }

  /**
   * 
   * @param {*} promises 
   * @returns 
   * 
   * 1. 返回一个promise
   * 2. 判断是否为数组,错误信息 Argument is not iterable
   * 3. 是否是空数组,是返回空数组
   * 4. 处理全部兑现
   *    4.1 记录结果:索引来记录结果,保证结果的顺序和Promise数组的顺序一致
   *    4.2 判断全部兑现: 通过兑现的次数进行判断,保证可以获取所有的结果
   * 5. 处理第一个拒绝
   */
  static all(promises) {
    return new Promise((resolve, reject) => {
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      promises.length === 0 && resolve(promises)
      const result = []
      let count = 0
      promises.forEach((p, index) => {
        this.resolve(p).then((res) => {
          result[index] = res
          count++
          count === promises.length && resolve(result)
        }, (err) => {
          reject(err)
        })
      })
    })
  }


  /**
   * Promise.allSettled
   * 1. 传入Promise都变成已敲定,既可获取兑现结果
   * 2. 数组判断,错误信息
   * 3. 空数组,直接兑现
   * 4. 等待全部敲定
   *    4.1 记录结果
   *    4.2 处理兑现:{state: 'fulfilled',value:1}
   *    4.3 处理拒绝:{state: 'rejected',reason:3}
   */

  static allSettled(promises){
    // 1. 返回一个promsie
    return new Promise((resolve, reject) => {
      // 2. 数组判断
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument is not iterable'))
      }
      // 3. 为空直接敲定
      promises.length === 0 && resolve(promises)
      // 4. 等待全部敲定
      // 4.1 记录结果:数组
      const result = []
      let count = 0 // 使用次数来记录是否全部敲定
      promises.forEach((p,index) => {
        this.resolve(p).then((res) => {
          // 4.2 处理兑现:{state: 'fulfilled',value:1} 
          result[index] = {state: FULFILLED, value:res}//  为保持返回数据和传入顺序一致,使用下标赋值
          count++
          count === promises.length && resolve(result)
        },err=>{
          // 4.2 处理拒绝
          result[index] = { state: REJECTED, reason: err}
          count++
          count === promises.length && resolve(result)

        })
      })
    })
  }

  /**
   * 静态方法 - any
   * 1. 返回promises,数组判断 错误信息Argument is not iterable
   * 2. 空数组 直接拒绝 拒绝原因:All promises were rejected[]
   * 3. 等待结果
   *    3.1 第一个兑现 
   *    3.2 全部拒绝 :All promises were rejected --[1,2,3]
   */
  static any (promises){
    // 1. 返回promises,数组判断 错误信息Argument is not iterable
    return new Promise((resolve,reject) => {
      if(!Array.isArray(promises)){
        return reject(new TypeError('Argument is not iterable'))
      }
      // 2. 空数组 直接拒绝
      promises.length === 0 && reject(new AggregateError(promises,'All promises were rejected'))
      // 3. 等待结果
      const errors = []
      let count = 0
      promises.forEach((p,index) => {
        this.resolve(p).then((res) => {
          // 3.1 第一个兑现 
          resolve(res)
        },err => {
          // 3.2 全部拒绝 :All promises were rejected --[1,2,3]
          errors[index] = err
          count++
          promises.length === count && reject(new AggregateError(errors,'All promises were rejected'))
        })
      })
    })

  }

}

// 处理 promise 重复调用问题
function resolvePromise(p2, x, resolve, reject) {
  if (x === p2) {
    throw new TypeError('chaining cycle deteted for promise #<Promise>')
  }
  // 判断是否是promise
  if (x instanceof Promise) {
    x.then((res) => {
      resolve(res)
    }, (err) => {
      reject(err)
    })
  } else if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      var then = x.then;
    } catch (err) {
      return reject(err)
    }
    if(typeof then === 'function') {
      let called = false
      try {
        then.call(x,y => {
          if(called) return;
          called = true;
          resolvePromise(p2, y ,resolve,reject)
        },r => {
          if(called) return;
          called = true;
          reject(r)
        })
      } catch (error) {
        if(called) return;
        called = true;
        reject(error)
      }
    } else {
      resolve(x)
    }
  } else {
    return resolve(x)
  }
}

// 定义函数
function runAsynctask(callback) {
  // 2. 调用核心api(queueMicrotask  MutationObserver  setTimeout)
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(callback)
  } else if (typeof MutationObserver === 'funciton') {
    const obs = new MutationObserver(callback)
    const div = document.createElement('div')
    obs.observe(div, { childList: true })
    div.innerHTML = 'test'
  } else {
    setTimeout(callback, 0)
  }
}