这次,终于搞定了Promise

266 阅读11分钟

1. 先搭建整体结构

要点:

  • 注意函数对象和原型对象上的方法,哪些是函数对象上的方法,哪些是原型对象上的方法
/**
 * 自定义Promise函数模块
 * 
 */

(function (params) {
  /**
   * Promise构造函数
   * excutor:执行器函数,同步执行
   */
  function Promise(excutor) {}

  /**
   * Promise原型对象的then()
   * 指定成功和失败的回调函数
   * 返回一个新的Promise对象
   */
  Promise.prototype.then = function(onResolved, onReject) {

  }

  /**
   * Promise原型对象的catch()
   * 指定失败的回调函数
   * 返回一个新的Promise
   */
  Promise.prototype.catch = function(onReject) {

  }

  /**
   * Promise函数对象的resolve方法
   * 返回一个指定结果数据成功的promise,值为value
   */
  Promise.resolve = function (value) {}

  /**
   * Promise函数对象的reject方法
   * 返回一个指定结果数据失败的promise,值为reason
   */
  Promise.reject = function (reason) {}

  /**
   * Promise函数对象的all方法
   * 返回一个promise,只有当所有都成功时才返回,否则只要有一个失败的就失败
   */
  Promise.all = function (promiseArr) {}

  /**
   * Promise函数对象的race方法
   * 返回一个promise,其结果由第一个完成的promise的结果决定
   */
  Promise.race = function (promiseArr) {}


  // 向外暴露Promise函数
  window.Promise = Promise
})(window)

2. 实现构造函数

要点:

  • 构造函数的入参是执行器函数,执行器函数有两个参数:resolve 和 reject,执行器函数是立即执行
  • 提前声明的变量:status:控制状态的改变,data:存储数据,callbacks,存储回调函数
  • 提前声明的函数:改变为成功状态的函数resolve() {} 和 改变为失败状态的函数reject() {}
/**
* Promise构造函数
* excutor:执行器函数,同步执行
*/
  function Promise(excutor) {

    this.status = 'pending'
    this.data = undefined
    this.callbacks = [] // 存储格式为{ onResolved, onRejected },也就是外界调用then函数时传递的参数

    function resolve(value) {}

    function reject(reason) {}

    excutor(resolve, reason)
  }

2.1 实现将状态改变为成功态的函数resolve()

要点:

  • 将状态从 'pending' 改变为 'resolved'
  • 将外界传递的value存储下来
  • !!!判断状态是否已经变为resolved,是则直接结束,保证外界多次调用resolve()函数时,只有第一次有效
  • !!!判断回调队列里是否已经有待执行的回调函数,有则 异步执行,为什么会有这种情况:当还未执行resolve函数时,就已经存储了成功的回调函数,也就是执行了then函数,then函数是同步执行。
  • !!!如果回调队列中有待执行的回调函数时,不能直接执行,如果直接执行的话,外界在new Promise时执行resolve时,回调便会执行,将违背回调异步执行的原理,所以需要异步执行回调函数,用setTimeout模拟异步队列
function resolve(value) {
  // 如果当前状态不是pending,直接结束
  if(this.status != 'pending') return
  // 将状态改为resolved
  this.status = 'resolved'
  // 保存value数据
  this.data = value
  // 如果有待执行的callback函数,立即异步执行回调
  if(this.callbacks.length) {
    setTimeout(() => { // 将回调函数放到宏任务的列表,保证异步执行,(模拟队列)
      this.callbacks.forEach(callbacksObj => {
        callbacksObj.onResolved(value)
      });
    })
  }
}

2.2 实现将状态改变为失败态的函数reject()

  • 实现基本与 resolve() 函数类似
function reject(reason) {
  // 如果当前状态不是pending,直接结束
  if(this.status != 'pending') return
  // 将状态改为rejected
  this.status = 'rejected'
  // 保存value数据
  this.data = reason
  // 如果有待执行的callback函数,立即异步执行回调
  if(this.callbacks.length) {
    setTimeout(() => { // 将回调函数放到宏任务的列表,保证异步执行,(模拟队列)
      this.callbacks.forEach(callbacksObj => {
        callbacksObj.onRejected(reason)
      });
    })
  }
}

2.3 考虑执行器函数执行期间 throw Error 的情况

  • 由于excutor函数执行期间,会有报错的情况,此时,状态由 'pending' 变为 'rejected', 所以直接执行 excutor(resolve, reject) 是不当的

  • 当catch捕获到错误时,可直接执行reject函数,改变状态

// 立即同步执行excutor
try {
  excutor(resolve, reject)
} catch (error) {
  reject(error)
}

3. 实现原型对象的then方法

要点:

  • then方法的功能是:通过制定成功或失败的回调函数,获取promise更改状态后的值
  • 参数:成功回调onResolved函数 和 失败回调 onRejected函数,都是异步执行
  • 返回值:返回一个新的promise对象,保证then的链式回调
  • 因为返回值是一个promise且保证链式调用,所以onResolved 和 onRejected 执行时有三种情况,分别处理三种对应的情况,传递给下一个then:
    1. 执行回调时抛出异常,状态变为rejected,返回error
    1. 如果回调执行结果返回的不是promise对象,状态变为resolved,返回value
    1. 如果回调执行结果返回的是promise对象,状态由这个promise决定,结果也是由这个promise决定

3.1 考虑入参和出参

Promise.prototype.then = function(onResolved, onRejected) {
  return new Promise((resolve, reject) => {
    // 通过调用当前的promise的resolve, reject,改变then的状态以及告诉外界返回值
  })
}

3.2 考虑三种状态下的处理逻辑(pending、resolved、rejected)

要点:

  • pending状态的情况是:当执行器中的resolve或reject异步执行或还未执行,先执行了then方法,此时要把回调函数存储在回调队列中(所以执行器中的resolve和reject要判断回调队列的长度)
  • resolved状态是:执行器中的resolve函数同步执行了,才执行then方法,此时要立即执行回调函数
  • rejected状态是:执行器中的reject函数同步执行了,才执行then方法,此时要立即执行回调函数
Promise.prototype.then = function(onResolved, onRejected) {
  return new Promise((resolve, reject) => {
    // 通过调用当前的promise的resolve, reject,改变then的状态以及告诉外界返回值

    const _this = this

    if (_this.status == PENDING) {
        // 当前状态还是pending状态,将回调函数存储起来,then先执行resolve还没执行时,就是pending状态
        _this.callbacks.push({
          onResolved,
          onRejected
        })
      }
      if (_this.status == RESOLVED) {
        setTimeout(() => {// 注意回调都是异步执行的
          onResolved()
        })
      }
      if (_this.status == REJECTED) {
        setTimeout(() => {// 注意回调都是异步执行的
          onRejected()
        })
      }
  })
}

此时的问题是回调函数直接执行后,并没有改变返回的promise的状态,也没有返回值

3.3 解决返回的promise的状态变更及返回值问题

要点:

  • 也就说要通过new的promise的resolve和reject方法来改变返回的promise对象的状态
  • 同时将 value 或者 reason 传递出去
  • 考虑上面说的三种情况,也就是说外界调用then方法时可能出现的三种情况:抛出异常、是一个promise对象、不是一个promise对象

先以 RESOLVED 状态下为例说明:

Promise.prototype.then = function(onResolved, onRejected) {
  
  const _this = this

  return new Promise((resolve, reject) => {
    // 通过调用当前的promise的resolve, reject,改变then的状态以及告诉外界返回值

    if (_this.status == RESOLVED) {
      setTimeout(() => {// 注意回调都是异步执行的
        // 情况一:try catch捕获抛出的异常,并且结果变为rejected
        try {
          const result = onResolved(_this.data)
          if (result instanceof Promise) {
            // 情况三:回调中返回的是一个promise对象,通过then方法获取结果到底是成功还是失败
            result.then(resolve, reject)
          } else {
            // 情况二:回调中返回的不是一个promise对象,
            // 通过返回的promise的resolve方法,将状态改变为resolved,并且告诉下一级此时是resolved,值是result
            resolve(result)
          }
        } catch (error) {
          //通过返回的promise的reject方法,将状态改变为rejected,并且告诉下一级此时是rejected,原因是error
          reject(error) 
        }
      })
    }
  })
}

REJECTED 状态与 RESOLVED 类似:

Promise.prototype.then = function(onResolved, onRejected) {

  const _this = this

  return new Promise((resolve, reject) => {
    // 通过调用当前的promise的resolve, reject,改变then的状态以及告诉外界返回值

    if (_this.status == REJECTED) {
      setTimeout(() => {// 注意回调都是异步执行的
        // 情况一:try catch捕获抛出的异常,并且结果变为rejected
        try {
          const result = onRejected(_this.data)
          if (result instanceof Promise) {
            // 情况三:回调中返回的是一个promise对象,通过then方法获取结果到底是成功还是失败
            result.then(resolve, reject)
          } else {
            // 情况二:回调中返回的不是一个promise对象,
            // 通过返回的promise的resolve方法,将状态改变为resolved,并且告诉下一级此时是resolved,值是result
            resolve(result)
          }
        } catch (error) {
          //通过返回的promise的reject方法,将状态改变为rejected,并且告诉下一级此时是rejected,原因是error
          reject(error) 
        }
      })
    }
  })
}

提取公共处理函数,优化then方法:

Promise.prototype.then = function(onResolved, onRejected) {
  const _this = this

  return new Promise((resolve, reject) => {
    // 通过调用当前的promise的resolve, reject,改变then的状态以及告诉外界返回值

    const handle = function (callback) {
      try {
        const result = callback(_this.data)
        if (result instanceof Promise) {
          result.then(resolve, reject)
        } else {
          resolve(result)
        }
      } catch (error) {
        reject(error)
      }
    }

    if (_this.status == RESOLVED) {
        setTimeout(() => {// 注意回调都是异步执行的
          handle(onResolved)
        })
      }

    if (_this.status == REJECTED) {
      setTimeout(() => {// 注意回调都是异步执行的
        handle(onRejected)
      })
    }
  })
}

此时还剩 PENDING 状态没有考虑,当前 PENDING 时的逻辑是存储回调函数,那什么时候执行呢? 已经存储到毁掉队列的回调函数,等待resolve或reject执行的时候执行。 所以then中的成功回调和失败回调迟早会执行,只是时机不同 然后只要执行,就需要考虑返回的promise的状态和值

if (_this.status == PENDING) {
  // 当前状态还是pending状态,将回调函数存储起来,then先执行resolve还没执行时,就是pending状态
  _this.callbacks.push({
    onResolved: () => {
      handle(onResolved)
    },
    onRejected: () => {
      handle(onRejected)
    }
  })
}

此时then的逻辑差不多完成了,整合一下就是: 主要逻辑就是封装函数handle处理返回的promise的状态变更以及值,分别处理三种状态下的逻辑

Promise.prototype.then = function(onResolved, onRejected) {
  const _this = this

  /**
   * then函数执行回调函数(onResolved, onRejected)时,有三种情况:
   * 1. 如果抛出异常,状态变为rejected,返回error
   * 2. 如果不是promise对象,状态变为resolved,返回value
   * 3. 如果是promise对象,状态由这个promise决定,结果也是由这个promise决定
   */
  /**
   * then函数中涉及到执行回调(onResolved, onRejected)的地方都要做handle处理,因为then返回的是一个promise
   * promise就要改变对应的状态,使用new的promise的resolve和reject来改变返回的promise对象的状态
   */
  return new Promise((resolve, reject) => {

    //封装处理执行回调函数时的三种情况,这样返回的promise对象可以正确的改变状态
    const handle = function (callback) {
      try {
        const result = callback(_this.data)
        if (result instanceof Promise) {
          result.then(resolve, reject)
        } else {
          resolve(result)
        }
      } catch (error) {
        reject(error)
      }
    }
    if (_this.status == PENDING) {
      // 当前状态还是pending状态,将回调函数存储起来,then先执行resolve还没执行时,就是pending状态
      _this.callbacks.push({
        onResolved: () => {
          handle(onResolved)
        },
        onRejected: () => {
          handle(onRejected)
        }
      })
    }
    if (_this.status == RESOLVED) {
      setTimeout(() => {
        handle(onResolved)
      })
    }
    if (_this.status == REJECTED) {
      setTimeout(() => {
        handle(onRejected)
      })
    }
  })
}

3.4 最后再处理一下参数的异常情况

/**
 * 处理参数的异常情况
 */
onResolved = typeof onResolved == 'function' ? onResolved : v => v
onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason }

有 reason => { throw reason } 这个才能做到异常穿透

then方法完整版

Promise.prototype.then = function(onResolved, onRejected) {
  const _this = this

  /**
   * 处理参数的异常情况
   */
  onResolved = typeof onResolved == 'function' ? onResolved : v => v
  onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason }

  return new Promise((resolve, reject) => {

    //封装处理执行回调函数时的三种情况,这样返回的promise对象可以正确的改变状态
    const handle = function (callback) {
      try {
        const result = callback(_this.data)
        if (result instanceof Promise) {
          result.then(resolve, reject)
        } else {
          resolve(result)
        }
      } catch (error) {
        reject(error)
      }
    }
    if (_this.status == PENDING) {
      // 当前状态还是pending状态,将回调函数存储起来,then先执行resolve还没执行时,就是pending状态
      _this.callbacks.push({
        onResolved: () => {
          handle(onResolved)
        },
        onRejected: () => {
          handle(onRejected)
        }
      })
    }
    if (_this.status == RESOLVED) {
      setTimeout(() => {
        handle(onResolved)
      })
    }
    if (_this.status == REJECTED) {
      setTimeout(() => {
        handle(onRejected)
      })
    }
  })
}

4. 实现原型对象的catch方法

要点:

  • 入参:onRejected回调,指定失败的回调函数
  • 出参:返回一个新的Promise
Promise.prototype.catch = function(onReject) {
  return this.then(undefined, onReject) // 必须指定成功的回调,哪怕是undefined
}

5. 实现函数对象的resolve方法

要点:

  • 入参:value,可以是一个基本类型,也可以是一个promise对象
  • 出参:
    • 返回一个指定结果数据成功或失败的promise
    • 如果是基本类型,状态变为resolved,返回值为value
    • 如果是promise对象,则返回promise对象执行的结果
/**
 * Promise函数对象的resolve方法
 * 返回一个指定结果数据成功的promise,值为value
 */
Promise.resolve = function (value) {
  return new Promise((resolve, reject) => {
    if (value instanceof Promise) {
      value.then(resolve, reject)
    } else {
      resolve(value)
    }
  })
}

6. 实现函数对象的reject方法

要点:

  • 入参:reason,具体返回的原因,基本类型
  • 出参:
    • 返回一个指定结果数据为失败的promise
/**
 * Promise函数对象的reject方法
 * 返回一个指定结果数据失败的promise,值为reason
 */
Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason)
  })
}

7. 实现函数对象的all方法

要点:

  • 功能:主要是等多个异步函数都完成后才返回
  • 入参:含多个promise对象的数组
  • 出参:返回一个promise,只有当所有都成功时才返回,否则只要有一个失败的就失败
Promise.all = function (promiseArr) {
  let resolvedCount = 0
  const values = new Array(promiseArr.length)
  return new Promise((resolve, reject) => {
    promiseArr.forEach((p,index) => {
      Promise.resolve(p).then(
        value => {
          resolvedCount++
          values[index] = value
          if (resolvedCount == promiseArr.length) {
            resolve(values)
          }
        },
        reason => {
          // 只要有失败的就执行reject
          reject(reason) 
        }
      )
    })
  })
}

该方法存在的问题是:当只要有一个失败的promise就执行reject,但是循环并没有中断,后面的成功的回调还是会执行,但是resolvedCount 和 promiseArr.length是不会相等的。

8. 实现函数对象的race方法

/**
 * Promise函数对象的race方法
 * 返回一个promise,其结果由第一个完成的promise的结果决定
 */
Promise.race = function (promiseArr) {
  return new Promise((resolve, reject) => {
    promiseArr.forEach((p, index) => {
      Promise.resolve(p).then(resolve, reject)
    })
  })
}