前言

很多 JavaScript 的初学者都曾感触过被回调天堂摆布的恐怖,直至把握了 Promise 语法才算解脱。尽管很多语言都早已内置了 Promise ,然而 JavaScript 中真正将其发扬光大的还是 jQuery 1.5 对 $.ajax 的重构,反对了 Promise,而且用法也和 jQuery 推崇的链式调用不约而同。起初 ES6 入世,大家才开始进入全民 Promise 的时代,再起初 ES8 又引入了 async 语法,让 JavaScript 的异步写法更加优雅。

明天咱们就一步一步来实现一个 Promise,如果你还没有用过 Promise,倡议先相熟一下 Promise 语法再来浏览本文。

构造函数

在已有的 Promise/A+ 标准中并没有规定 promise 对象从何而来,在 jQuery 中通过调用 $.Deferred() 失去 promise 对象,ES6 中通过实例化 Promise 类失去 promise 对象。这里咱们应用 ES 的语法,结构一个类,通过实例化的形式返回 promise 对象,因为 Promise 曾经存在,咱们临时给这个类取名为 Deferred

class Deferred {  constructor(callback) {    const resolve = () => {      // TODO    }    const reject = () => {      // TODO    }    try {      callback(resolve, reject)    } catch (error) {      reject(error)    }  }}

构造函数承受一个 callback,调用 callback 的时候需传入 resolve、reject 两个办法。

Promise 的状态

Promise 一共分为三个状态:

  • pending:期待中,这是 Promise 的初始状态;
  • ????♂️fulfilled:已完结,失常调用 resolve 的状态;
  • ????♂️rejected:已回绝,外部呈现谬误,或者是调用 reject 之后的状态;

咱们能够看到 Promise 在运行期间有一个状态,存储在 [[PromiseState]] 中。上面咱们为 Deferred 增加一个状态。

//根底变量的定义const STATUS = {  PENDING: 'PENDING',  FULFILLED: 'FULFILLED',  REJECTED: 'REJECTED'}class Deferred {  constructor(callback) {    this.status = STATUS.PENDING    const resolve = () => {      // TODO    }    const reject = () => {      // TODO    }    try {      callback(resolve, reject)    } catch (error) {      // 出现异常间接进行 reject      reject(error)    }  }}

这里还有个有意思的事件,晚期浏览器的实现中 fulfilled 状态是 resolved,显著与 Promise 标准不符。当然,当初曾经修复了。

外部后果

除开状态,Promise 外部还有个后果 [[PromiseResult]],用来暂存 resolve/reject 承受的值。

持续在构造函数中增加一个外部后果。

class Deferred {  constructor(callback) {    this.value = undefined    this.status = STATUS.PENDING    const resolve = value => {      this.value = value      // TODO    }    const reject = reason => {      this.value = reason      // TODO    }    try {      callback(resolve, reject)    } catch (error) {      // 出现异常间接进行 reject      reject(error)    }  }}

贮存回调

应用 Promise 的时候,咱们个别都会调用 promise 对象的 .then 办法,在 promise 状态转为 fulfilledrejected 的时候,拿到外部后果,而后做后续的解决。所以构造函数中,还须要结构两个数组,用来存储 .then 办法传入的回调。

class Deferred {  constructor(callback) {    this.value = undefined    this.status = STATUS.PENDING    this.rejectQueue = []    this.resolveQueue = []    const resolve = value => {      this.value = value      // TODO    }    const reject = reason => {      this.value = reason      // TODO    }    try {      callback(resolve, reject)    } catch (error) {      // 出现异常间接进行 reject      reject(error)    }  }}

resolvereject

批改状态

接下来,咱们须要实现 resolve 和 reject 两个办法,这两个办法在被调用的时候,会扭转 promise 对象的状态。而且任意一个办法在被调用之后,另外的办法是无奈被调用的。

new Promise((resolve, reject) => {    setTimeout(() => {    resolve('????♂️')  }, 500)  setTimeout(() => {    reject('????♂️')  }, 800)}).then(  () => {    console.log('fulfilled')  },  () => {    console.log('rejected')  })

此时,控制台只会打印出 fulfilled,并不会呈现 rejected

class Deferred {  constructor(callback) {    this.value = undefined    this.status = STATUS.PENDING    this.rejectQueue = []    this.resolveQueue = []    let called // 用于判断状态是否被批改    const resolve = value => {            if (called) return      called = true      this.value = value      // 批改状态      this.status = STATUS.FULFILLED    }    const reject = reason => {            if (called) return      called = true      this.value = reason      // 批改状态      this.status = STATUS.REJECTED    }    try {      callback(resolve, reject)    } catch (error) {      // 出现异常间接进行 reject      reject(error)    }  }}

调用回调

批改完状态后,拿到后果的 promise 个别会调用 then 办法传入的回调。

class Deferred {  constructor(callback) {    this.value = undefined    this.status = STATUS.PENDING    this.rejectQueue = []    this.resolveQueue = []    let called // 用于判断状态是否被批改    const resolve = value => {            if (called) return      called = true      this.value = value      // 批改状态      this.status = STATUS.FULFILLED      // 调用回调      for (const fn of this.resolveQueue) {        fn(this.value)      }    }    const reject = reason => {            if (called) return      called = true      this.value = reason      // 批改状态      this.status = STATUS.REJECTED      // 调用回调      for (const fn of this.rejectQueue) {        fn(this.value)      }    }    try {      callback(resolve, reject)    } catch (error) {      // 出现异常间接进行 reject      reject(error)    }  }}

相熟 JavaScript 事件零碎的同学应该晓得,promise.then 办法中的回调会被搁置到微工作队列中,而后异步调用。

所以,咱们须要将回调的调用放入异步队列,这里咱们能够放到 setTimeout 中进行提早调用,尽管不太符合规范,然而将就将就。

class Deferred {  constructor(callback) {    this.value = undefined    this.status = STATUS.PENDING    this.rejectQueue = []    this.resolveQueue = []    let called // 用于判断状态是否被批改    const resolve = value => {            if (called) return      called = true      // 异步调用      setTimeout(() => {          this.value = value        // 批改状态        this.status = STATUS.FULFILLED        // 调用回调        for (const fn of this.resolveQueue) {          fn(this.value)        }      })    }    const reject = reason => {            if (called) return      called = true      // 异步调用      setTimeout(() =>{        this.value = reason        // 批改状态        this.status = STATUS.REJECTED        // 调用回调        for (const fn of this.rejectQueue) {          fn(this.value)        }      })    }    try {      callback(resolve, reject)    } catch (error) {      // 出现异常间接进行 reject      reject(error)    }  }}

then 办法

接下来咱们须要实现 then 办法,用过 Promise 的同学必定晓得,then 办法是可能持续进行链式调用的,所以 then 必须要返回一个 promise 对象。然而在 Promise/A+ 标准中,有明确的规定,then 办法返回的是一个新的 promise 对象,而不是间接返回 this,这一点咱们能够通过上面代码验证一下。

能够看到 p1 对象和 p2 是两个不同的对象,并且 then 办法返回的 p2 对象也是 Promise 的实例。

除此之外,then 办法还须要判断以后状态,如果以后状态不是 pending 状态,则能够间接调用传入的回调,而不必再放入队列进行期待。

class Deferred {  then(onResolve, onReject) {    if (this.status === STATUS.PENDING) {      // 将回调放入队列中      const rejectQueue = this.rejectQueue      const resolveQueue = this.resolveQueue      return new Deferred((resolve, reject) => {        // 暂存到胜利回调期待调用        resolveQueue.push(function (innerValue) {          try {            const value = onResolve(innerValue)            // 扭转以后 promise 的状态            resolve(value)          } catch (error) {            reject(error)          }        })        // 暂存到失败回调期待调用        rejectQueue.push(function (innerValue) {          try {            const value = onReject(innerValue)            // 扭转以后 promise 的状态            resolve(value)          } catch (error) {            reject(error)          }        })      })    } else {      const innerValue = this.value      const isFulfilled = this.status === STATUS.FULFILLED      return new Deferred((resolve, reject) => {        try {          const value = isFulfilled            ? onResolve(innerValue) // 胜利状态调用 onResolve            : onReject(innerValue) // 失败状态调用 onReject          resolve(value) // 返回后果给前面的 then        } catch (error) {          reject(error)        }      })    }  }}

当初咱们的逻辑曾经能够根本跑通,咱们先试运行一段代码:

new Deferred(resolve => {  setTimeout(() => {    resolve(1)  }, 3000)}).then(val1 => {  console.log('val1', val1)  return val1 * 2}).then(val2 => {  console.log('val2', val2)  return val2})

3 秒后,控制台呈现如下后果:

能够看到,这根本合乎咱们的预期。

值穿透

如果咱们在调用 then 的时候,如果没有传入任何的参数,依照标准,以后 promise 的值是能够透传到下一个 then 办法的。例如,如下代码:

new Deferred(resolve => {  resolve(1)})  .then()  .then()  .then(val => {    console.log(val)  })

在控制台并没有看到任何输入,而切换到 Promise 是能够看到正确后果的。

要解决这个办法很简略,只须要在 then 调用的时候判断参数是否为一个函数,如果不是则须要给一个默认值。

const isFunction = fn => typeof fn === 'function'class Deferred {  then(onResolve, onReject) {    // 解决值穿透    onReject = isFunction(onReject) ? onReject : reason => { throw reason }    onResolve = isFunction(onResolve) ? onResolve : value => { return value }    if (this.status === STATUS.PENDING) {      // ...    } else {      // ...    }  }}

当初咱们曾经能够拿到正确后果了。

一步之遥

当初咱们间隔完满实现 then 办法只差一步之遥,那就是咱们在调用 then 办法传入的 onResolve/onReject 回调时,还须要判断他们的返回值。如果回调的外部返回的就是一个 promise 对象,咱们应该如何解决?或者呈现了循环援用,咱们又该怎么解决?

后面咱们在拿到 onResolve/onReject 的返回值后,间接就调用了 resolve 或者 resolve,当初咱们须要把他们的返回值进行一些解决。

then(onResolve, onReject) {  // 解决值穿透代码曾经省略  if (this.status === STATUS.PENDING) {    // 将回调放入队列中    const rejectQueue = this.rejectQueue    const resolveQueue = this.resolveQueue    const promise = new Deferred((resolve, reject) => {      // 暂存到胜利回调期待调用      resolveQueue.push(function (innerValue) {        try {          const value = onResolve(innerValue)-         resolve(value)+         doThenFunc(promise, value, resolve, reject)        } catch (error) {          reject(error)        }      })      // 暂存到失败回调期待调用      rejectQueue.push(function (innerValue) {        try {          const value = onReject(innerValue)-         resolve(value)+         doThenFunc(promise, value, resolve, reject)        } catch (error) {          reject(error)        }      })    })    return promise  } else {    const innerValue = this.value    const isFulfilled = this.status === STATUS.FULFILLED    const promise = new Deferred((resolve, reject) => {      try {        const value = isFulfilled        ? onResolve(innerValue) // 胜利状态调用 onResolve        : onReject(innerValue) // 失败状态调用 onReject-       resolve(value)+       doThenFunc(promise, value, resolve, reject)      } catch (error) {        reject(error)      }    })    return promise  }}

返回值判断

在咱们应用 Promise 的时候,常常会在 then 办法中返回一个新的 Promise,而后把新的 Promise 实现后的外部后果再传递给前面的 then 办法。

fetch('server/login')    .then(user => {      // 返回新的 promise 对象      return fetch(`server/order/${user.id}`)    })    .then(order => {      console.log(order)    })
function doThenFunc(promise, value, resolve, reject) {  // 如果 value 是 promise 对象  if (value instanceof Deferred) {    // 调用 then 办法,期待后果    value.then(      function (val) {          doThenFunc(promise, value, resolve, reject)        },      function (reason) {        reject(reason)      }    )    return  }  // 如果非 promise 对象,则间接返回  resolve(value)}

判断循环援用

如果以后 then 办法回调函数返回值是以后 then 办法产生的新的 promise 对象,则被认为是循环援用,具体案例如下:

then 办法返回的新的 promise 对象 p1,在回调中被当做返回值,此时会抛出一个异样。因为依照之前的逻辑,代码将会始终困在这一段逻辑里。

所以,咱们须要提前预防,及时抛出谬误。

function doThenFunc(promise, value, resolve, reject) {  // 循环援用  if (promise === value) {    reject(        new TypeError('Chaining cycle detected for promise')    )    return  }  // 如果 value 是 promise 对象  if (value instanceof Deferred) {    // 调用 then 办法,期待后果    value.then(      function (val) {          doThenFunc(promise, value, resolve, reject)        },      function (reason) {        reject(reason)      }    )    return  }  // 如果非 promise 对象,则间接返回  resolve(value)}

当初咱们再试试在 then 中返回一个新的 promise 对象。

const delayDouble = (num, time) => new Deferred((resolve) => {  console.log(new Date())  setTimeout(() => {    resolve(2 * num)  }, time)})new Deferred(resolve => {  setTimeout(() => {    resolve(1)  }, 2000)})  .then(val => {    console.log(new Date(), val)    return delayDouble(val, 2000)  })  .then(val => {    console.log(new Date(), val)  })

下面的后果也是完满合乎咱们的预期。

catch 办法

catch 办法其实很简略,相当于 then 办法的一个简写。

class Deferred {  constructor(callback) {}  then(onResolve, onReject) {}  catch(onReject) {    return this.then(null, onReject)  }}

静态方法

resolve/reject

Promise 类还提供了两个静态方法,间接返回状态曾经固定的 promise 对象。

class Deferred {  constructor(callback) {}  then(onResolve, onReject) {}  catch(onReject) {}    static resolve(value) {    return new Deferred((resolve, reject) => {      resolve(value)    })  }  static reject(reason) {    return new Deferred((resolve, reject) => {      reject(reason)    })  }}

all

all 办法承受一个 promise 对象的数组,等数组中所有的 promise 对象的状态变为 fulfilled,而后返回后果,其后果也是一个数组,数组的每个值对应的是 promise 对象的外部后果。

首先,咱们须要先判断传入的参数是否为数组,而后结构一个后果数组以及一个新的 promise 对象。

class Deferred {  static all(promises) {    // 非数组参数,抛出异样    if (!Array.isArray(promises)) {      return Deferred.reject(new TypeError('args must be an array'))    }        // 用于存储每个 promise 对象的后果    const result = []    const length = promises.length    // 如果 remaining 归零,示意所有 promise 对象曾经 fulfilled    let remaining = length     const promise = new Deferred(function (resolve, reject) {      // TODO    })        return promise  }}

接下来,咱们须要进行一下判断,对每个 promise 对象的 resolve 进行拦挡,每次 resolve 都须要将 remaining 减一,直到 remaining 归零。

class Deferred {  static all(promises) {    // 非数组参数,抛出异样    if (!Array.isArray(promises)) {      return Deferred.reject(new TypeError('args must be an array'))    }    const result = [] // 用于存储每个 promise 对象的后果    const length = promises.length    let remaining = length    const promise = new Deferred(function (resolve, reject) {      // 如果数组为空,则返回空后果      if (promises.length === 0) return resolve(result)      function done(index, value) {        doThenFunc(          promise,          value,          (val) => {            // resolve 的后果放入 result 中            result[index] = val            if (--remaining === 0) {              // 如果所有的 promise 都曾经返回后果              // 而后运行前面的逻辑              resolve(result)            }          },          reject        )      }      // 放入异步队列      setTimeout(() => {        for (let i = 0; i < length; i++) {          done(i, promises[i])        }      })    })        return promise  }}

上面咱们通过如下代码,判断逻辑是否正确。依照预期,代码运行后,在 3 秒之后,控制台会打印一个数组 [2, 4, 6]

const delayDouble = (num, time) => new Deferred((resolve) => {  setTimeout(() => {    resolve(2 * num)  }, time)})console.log(new Date())Deferred.all([  delayDouble(1, 1000),  delayDouble(2, 2000),  delayDouble(3, 3000)]).then((results) => {  console.log(new Date(), results)})

下面的运行后果,根本合乎咱们的预期。

race

race 办法同样承受一个 promise 对象的数组,然而它只须要有一个 promise 变为 fulfilled 状态就会返回后果。

class Deferred {  static race(promises) {    if (!Array.isArray(promises)) {      return Deferred.reject(new TypeError('args must be an array'))    }    const length = promises.length    const promise = new Deferred(function (resolve, reject) {      if (promises.length === 0) return resolve([])      function done(value) {        doThenFunc(promise, value, resolve, reject)      }      // 放入异步队列      setTimeout(() => {        for (let i = 0; i < length; i++) {          done(promises[i])        }      })    })    return promise  }}

上面咱们将后面验证 all 办法的案例改成 race。依照预期,代码运行后,在 1 秒之后,控制台会打印一个2。

const delayDouble = (num, time) => new Deferred((resolve) => {  setTimeout(() => {    resolve(2 * num)  }, time)})console.log(new Date())Deferred.race([  delayDouble(1, 1000),  delayDouble(2, 2000),  delayDouble(3, 3000)]).then((results) => {  console.log(new Date(), results)})

下面的运行后果,根本合乎咱们的预期。

总结

一个简易版的 Promise 类就曾经实现了,这里还是省略了局部细节,残缺代码能够拜访 github。Promise 的呈现为前期的 async 语法打下了坚实基础,下一篇博客能够好好聊一聊 JavaScript 的异步编程史,不小心又给本人挖坑了。。。