Promise 在开发中我们经常用到,它解决了回调地狱问题,对错误的处理也非常方便。本文我将通过一步步完善 Promise,从简单到复杂的过程来说。
本文适合熟练运用 Promise 的人阅读。
极简版
首先Promise是一个类,类中的构造函数需要接收一个执行函数executor默认就会执行,它有两个参数:resolve和reject,这两个参数是Promise内部定义的两个函数,用来改变状态并执行对应回调函数。
默认创建一个Promise状态就是pending,promise只有三种状态:pending,fulfilled,rejected,调用成功resolve和失败reject方法时,需要传递一个成功的原因/值value和失败的原因reason。每一个promise实例都有一个 then 方法。
Promise状态一经改变就不能再改变,故我们限制只能在状态为pending才改变,这样就保证状态只能改变一次。
如果抛出异常按照失败来处理。
按照以上Promise的基本要求,就有个基本结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
   | const STATUS = {   PENDING: 'PENDING',   FULFILLED: 'FULFILLED',   REJECTED: 'REJECTED', }
  class Promise {   constructor(executor) {     this.status = STATUS.PENDING     this.value = undefined // 成功的值     this.reason = undefined // 失败原因     this.onResolvedCallbacks = [] // 存放成功的回调     this.onRejectedCallbacks = [] // 存放失败的回调
      const resolve = val => {       if (this.status === STATUS.PENDING) {         this.status = STATUS.FULFILLED         this.value = val         this.onResolvedCallbacks.forEach(fn => fn())       }     }
      const reject = reason => {       if (this.status === STATUS.PENDING) {         this.status = STATUS.REJECTED         this.reason = reason         this.onRejectedCallbacks.forEach(fn => fn())       }     }
      try {       executor(resolve, reject)     } catch (e) {       console.log(e)     }   }
    then(onFulfilled, onRejected) {     if (this.status === STATUS.FULFILLED) {       onFulfilled(this.value)     }     if (this.status === STATUS.REJECTED) {       onRejected(this.reason)     }     if (this.status === STATUS.PENDING) {       this.onResolvedCallbacks.push(() => {         onFulfilled(this.value)       })       this.onRejectedCallbacks.push(() => {         onRejected(this.reason)       })     }   } }
  | 
上面代码中,为什么不直接执行回调还要存储呢?因为如果当resolve(3)被延迟执行时,此时常理写代码来说 then 是会被执行的,但此时没有resolve,故p的状态应为pending,不应立即执行成功调用的函数,需要把它存起来,直到执行resolve再执行成功调用的函数。
1 2 3 4 5 6 7 8 9
   | let p = new Promise((resolve, reject) => {   setTimeout(() => {     resolve(3)   }, 1000) }) p.then(res => {   console.log('then1', res)   return 2 })
  | 
链式调用
链式调用想必大家多少有点了解,在 jQuery 里面的链式调用则是返回 this,而 Promise 里面的链接调用则是返回一个新的 Promise 对象。
1 2 3 4 5 6 7 8 9 10 11 12
   | let p = new Promise((resolve, reject) => {   setTimeout(() => {     resolve(3)   }, 1000) }) p.then(res => {   console.log('then1', res)    return 2 }).then(res => {      console.log('then2', res)  })
  | 
修改then方法和增加catch方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   | then(onFulfilled, onRejected) {   let nextPromise = new Promise((resolve, reject) => {     if (this.status === STATUS.FULFILLED) {       setTimeout(() => {         try {           let res = onFulfilled(this.value)           resolve(res)         } catch (e) {           reject(e)         }       })     }     if (this.status === STATUS.REJECTED) {       setTimeout(() => {         try {           let res = onRejected(this.reason)           resolve(res)         } catch (e) {           reject(e)         }       })     }     if (this.status === STATUS.PENDING) {       this.onResolvedCallbacks.push(() => {         setTimeout(() => {           try {             let res = onFulfilled(this.value)             resolve(res)           } catch (e) {             reject(e)           }         })       })       this.onRejectedCallbacks.push(() => {         setTimeout(() => {           try {             let res = onRejected(this.reason)             resolve(res)           } catch (e) {             reject(e)           }         })       })     }   })   return nextPromise }
  catch(err) {      return this.then(undefined, err) }
  | 
此次修改我们是在最外层包了新的Promise,然后加了个 setTimeout 模拟微任务(因为这里用的 setTimeout 模拟微任务,所以 JS 事件循环执行顺序上和原生 Promise 有区别),把回调放入,等待确保异步执行。
链式调用进阶版
上面的Promise依旧没有过关,因为如果链式调用中Promise返回的是普通值,就应该把值包装成新的Promise对象
- 每个 then 方法都返回一个新的 Promise 对象(重点)
 - 如果 then 方法返回了一个 Promise 对象,则需要查看它的状态,如果状态是成功,则调用
resolve方法,把成功的状态传递给它;如果是失败的,则把失败的状态传递给下一个Promise对象。 - 如果 then 方法中返回的是一个原始数据类型值(如 Number、String 等)就使用此值包装成一个新的 Promise 对象返回。
 - 如果 then 方法中没有 return 语句,则返回一个用 undefined 包装的 Promise 对象
 - 如果 then 方法没有传入任何回调,则继续向下传递(值的传递特性)。
 - 如果是循环引用则需要抛出错误
 
修改then方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
   | then(onFulfilled, onRejected) {   onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x   onRejected =     typeof onRejected === 'function'       ? onRejected       : err => {           throw err         }   let nextPromise = new Promise((resolve, reject) => {     if (this.status === STATUS.FULFILLED) {       setTimeout(() => {         try {           let res = onFulfilled(this.value)           resolvePromise(res, nextPromise, resolve, reject)         } catch (e) {           reject(e)         }       })     }
      if (this.status === STATUS.REJECTED) {       setTimeout(() => {         try {           let res = onRejected(this.reason)           resolvePromise(res, nextPromise, resolve, reject)         } catch (e) {           reject(e)         }       })     }     if (this.status === STATUS.PENDING) {       this.onResolvedCallbacks.push(() => {         setTimeout(() => {           try {             let res = onFulfilled(this.value)             resolvePromise(res, nextPromise, resolve, reject)           } catch (e) {             reject(e)           }         })       })       this.onRejectedCallbacks.push(() => {         setTimeout(() => {           try {             let res = onRejected(this.reason)             resolve(res)           } catch (e) {             reject(e)           }         })       })     }   })   return nextPromise }
  | 
里面增加了一个resolvePromise函数,处理调用then方法后不同返回值的情况 ,实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   | function resolvePromise(x, nextPromise, resolve, reject) {   if (x === nextPromise) {          return reject(new TypeError('循环引用'))   } else if (x && (typeof x === 'object' || typeof x === 'function')) {     
      let called = false      try {       let then = x.then        if (typeof then === 'function') {                  then.call(           x,           function (y) {                          if (called) return             called = true             resolvePromise(y, nextPromise, resolve, reject)            },           function (r) {             if (called) return             called = true             reject(r)            }         )       } else {         resolve(x)        }     } catch (e) {       reject(e)     }   } else {          resolve(x)   } }
  | 
Promise.resolve() 实现
1 2 3 4 5 6 7 8
   | class Promise {      static resolve(val) {     return new Promise((resolve, reject) => {       resolve(val)     })   } }
  | 
Promise.reject() 实现
1 2 3 4 5 6 7 8
   | class Promise {      static reject(reason) {     return new Promise((resolve, reject) => {       reject(reason)     })   } }
  | 
Promise.prototype.finally 实现
该方法主要有两大重点
- 无论当前这个 Promise 对象最终的状态是成功还是失败 ,finally 方法里的回调函数都会执行一次
 - 在 finally 方法后面可以继续链式调用 then 方法,拿到当前这个 Promise 对象最终返回的结果
 
1 2 3 4 5 6 7 8 9 10 11 12 13
   | Promise.prototype.finally = function (callback) {   return this.then(     data => {              return Promise.resolve(callback()).then(() => data)     },     err => {       return Promise.resolve(callback()).then(() => {         throw err       })     }   ) }
  | 
Promise.all() 实现
Promise.all 可以将多个 Promise 实例包装成一个新的 Promise 实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | function isPromise(val) {   return val && typeof val.then === 'function' }
  Promise.all = function (promises) {   if (!Array.isArray(promises)) {     throw new Error('Not a array')   }   return new Promise((resolve, reject) => {     let result = []     let times = 0      function processData(index, val) {       result[index] = val       if (++times === promises.length) {         resolve(result)       }     }     for (let i = 0; i < promises.length; i++) {       let p = promises[i]       if (isPromise(p)) {                  p.then(data => {           processData(i, data)         }, reject)       } else {         processData(i, p)        }     }   }) }
  | 
Promise.race() 实现
在执行多个异步操作中,只保留取第一个执行完成的异步操作的结果,即是哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。其他的方法仍在执行,不过执行结果会被抛弃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | Promise.race = function (promises) {   if (!Array.isArray(promises)) {     throw new Error('Not a array')   }   return new Promise((resolve, reject) => {     if (promises.length === 0) {       return     } else {       for (let p of promises) {         resolve(p).then(           value => {             resolve(value)           },           reason => {             reject(reason)           }         )       }     }   }) }
  | 
到此为止,Promise 就实现完成了。
完整源码地址