全程演绎手写实现符合 Promises/A+规范的整个过程

356 阅读11分钟

模块化封装

由于是希望此Promise是用来应用到页面上的,所以还是使用ES5中实现模块的方式来写,即:使用立即执行函数来模拟模块化。然后将Promise构造函数放到window的属性上

// Promise.js文件
(function (obj) { 
// 注意:此处obj是形参,为了和实参window区别,所以故意不用window

  function Promise() {

  }
  
  // 将Promise放到传递进来的对象上,作为对象的方法
  obj.Promise = Promise
})(window)

声明构造函数

// Promise.js文件
(function (obj) {

  /* 
    声明构造函数
    并接收执行器函数excutor作为参数
  */
  function Promise(excutor) {

  }
  
  obj.Promise = Promise
})(window)

添加所有方法

  • 有些方法在原型上,用来给实例对象使用的
  • 有些方法直接在构造函数上,算是语法糖,可以直接通过构造函数调用。
  • 还有各方法的形参,关于这些形参想必熟悉Promise的小伙伴应该都懂得他们的含义
// Promise.js文件
(function (obj) {

  function Promise(excutor) {

  }
  
  Promise.prototype.then = function (onResovled, onRejected) {

  }

  Promise.prototype.catch = function (onRejected) {

  }

  Promise.resolve = function (value) {
    
  }
  Promise.reject = function (reason) {
    
  }
  Promise.all = function (promises) {
    
  }
  Promise.race = function (promises) {
    
  }
  
  obj.Promise = Promise
})(window)

实现构造函数

需要结合怎样使用Promise,来考虑构造函数如何实现

// userPromise.js文件
new Promise((resolve, reject) => {
  /* 
    resolve和reject都是由Promise内部提供的,这里只是形参,
    起占位作用,以备在执行器函数内部调用
  */
  console.log('执行器内部代码!同步的');
})
console.log('Promise构造函数后面代码!'); // 可以通过添加此代码证明执行器内部代码是否是同步执行的
// Promise.js文件
function Promise(excutor) {
    // 定义resolve函数,并接成功的结果
    function resolve(value) {

    }
    // 定义reject函数,并接被拒绝的原因
    function reject(reason) {

    }

    // 立即同步执行执行器函数,并传递实参函resolve和reject
    excutor(resolve, reject)
}


给Promise实例对象添加三个属性

// 在立即执行函数这一层先声明以下三个常量
(function (obj) {
  // 声明状态常量
  const PENDING = 'pending'
  const RESOLVED = 'resolved'
  const REJECTED = 'rejected'
  ...
})(window)
// Promise.js文件
function Promise(excutor) {
    // 用来保存Promise状态的属性,初始值为pending,可以修改为resolved或rejected
    this.status = PENDING
    // 用来保存成功或被拒绝后结果的属性
    this.data = undefined
    // 用来保存多个回调函数对象(成功和被拒绝)的数组
    // 每个对象的结构:{ onResoled() {}, onRejected() {} }
    this.callbacks = []
}

书写resolve函数和reject函数

// Promise.js文件
// 定义resolve函数,并接成功的结果
function resolve(value) {
  // 状态只能改一次,非peding状态,直接返回
  if (this.status !== PENDING) return
  
  /* 
    此函数被调用后需要做三件事情
    1、修改Promise的状态为resolved
    2、保存传递进来的结果到data中
    3、如果使用方是先设置回调函数后修改状态的话,
    也就意味着可以直接遍历调用callbacks里面的回调函数了
    也就是then指定的那些回调函数,是异步执行的。
    后面会在then方法中向callbacks中push回调函数
  */
  this.status = RESOLVED
  this.data = value
  setTimeout(() => {
    this.callbacks.forEach(obj => {
        obj.onResovled(this.data)
    })
  })
}
// 定义reject函数,并接被拒绝的原因
function reject(reason) {
  // 状态只能改一次,非peding状态,直接返回
  if (this.status !== PENDING) return
  
  // 同理
  this.status = REJECTED
  this.data = reason
  setTimeout(() => {
    this.callbacks.forEach(obj => {
      obj.onRejected(this.data)
    })
  })
}

捕获执行器内部代码抛出的异常

要考虑执行异步任务的时候抛出异常的情况

// usePromise.js文件
const p = new Promise((resolve, reject) => {
   // 也就是这里使用的时候可能抛出异常
})
// Promise.js文件
// 要考虑捕获执行器抛出异常
try {
  excutor(resolve, reject)
} catch (error) {
  reject(error) // 抛出异常的情况下自然需要调用被拒绝的函数reject
}

书写then方法

当状态为pending状态的情况下,即使已经指定了回调函数,还不能调用,需要等状态的变化,这个时候就需要先把回调函数保存起来了。正好跟上面resolve函数对应起来,当先指定了回调函数,后改变状态的时候。就需要先把回调函数保存起来了。

// Promise.js文件
Promise.prototype.then = function (onResovled, onRejected) {
    // 保存回调函数
    switch(this.status) {
      case PENDING:
        this.callbacks.push({
          onResovled,
          onRejected
        })
        break
    }
}

测试刚才的代码

注意这里的条件是先指定回调函数,后修改状态,所以在执行器中需要通过定时器做一下延时调用相应的函数

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功的结果!')
  }, 1000)
})

p.then(
  value => {
    console.log('onResolved1: ' + value);
  },
  reason => {
    console.log('onRejected1: ' + reason);
  }
)
p.then(
  value => {
    console.log('onResolved2: ' + value);
  },
  reason => {
    console.log('onRejected2: ' + reason);
  }
)
// 打印结果:
onResolved1: 成功的结果!
onResolved2: 成功的结果!

解决this指向问题

// 将resolve方法和reject方法修改成箭头函数,解决this指向问题

function resolve(value) { ... }
// 修改为
const resolve = value => { ... }

function reject(reason) { ... }
// 修改为
const reject = reason =>  { ... }

被拒绝结果的测试


const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('被拒绝的结果!')
  }, 1000)
})
// 打印结果:
onRejected1: 被拒绝的结果!
onRejected2: 被拒绝的结果!

测试then的回调函数是否为异步

console.log('then方法后面的代码!');
// 打印结果:
then方法后面的代码!
onResolved1: 成功的结果!
onResolved2: 成功的结果!

打印的顺序说明then的回调函数是异步执行的


继续完善then方法——考虑状态不为pending的情况

当状态为resolvedrejected的时候异步调用onResovled函数或onRejected函数

Promise.prototype.then = function (onResovled, onRejected) {
    // 保存回调函数
    switch(this.status) {
      case PENDING:
        this.callbacks.push({
          onResovled,
          onRejected
        })
        break
      // 状态为resolved的时候异步调用onResovled函数
      case RESOLVED:
        setTimeout(() => {
          onResovled(this.data)
        })
        break
      // 状态为rejected的时候异步调用onRejected函数
      case REJECTED:
        setTimeout(() => {
          onRejected(this.data)
        })
        break
    }
}

继续完善then方法——要返回一个新的promise实例对象

  • 首先需要用一个实例化的Promise构造函数把then方法里面的所有代码包含在内,并且返回此promise实例对象
  • 返回的新的promise实例对象的状态和结果取决于 promise中执行器excutor 的执行结果或者前面的 then中的回调函数onResovledonRejected 执行的结果所以需要考虑以下三种情况。以下为了书写方便把以上两种情况统称为 前面
    1. 前面 抛出异常:新promise状态就该为rejected,结果为error
    2. 前面 返回非promise类型的值:新promise状态就该为resovled,结果为此值
    3. 前面 返回promise类型的值:新promise状态和结果就该为 前面 promise的状态和结果

问:为什么上面要提到 promise中执行器excutorthen中的回调函数onResovledonRejected 两种情况?

答:因为我们不仅要考虑第一次实例化Promise构造函数后调用then方法,而且还要考虑then的链式调用。

基于以上思路,添加以下代码

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

    // 返回一个新的promise实例对象
    return new Promise(resolve, reject) {
      switch(this.status) {
        ...
        case RESOLVED:
          setTimeout(() => {
            
            try { // a、正常情况
              // 将结果保存起来判断是否为promise类型的结果
              const result = onResovled(this.data)
              if (result instanceof Promise) {
                // 3、前面返回promise类型的值:新promise状态和结果就该为前面promise的状态和结果
                // 要获取前面的promise的结果就需要调用前面promise的then方法
                /* result.then(
                  // 在前面promise的then方法里面的成功的回调函数里面
                  // 调用新的promise的resolve方法,并传递值
                  value => {
                    resolve(value)
                  },
                  // 在前面promise的then方法里面的被拒绝的回调函数里面
                  // 调用新的promise的reject方法,并传递值
                  reason => {
                    reject(reason)
                  }
                ) */
                // 以上代码的简化版
                result.then(resolve, reject)
              } else {
                // 2、前面返回非promise类型的值:新promise状态就该为resovled,结果为此值
                resolve(result)
              }
            } catch (error) { // b、异常情况
              // 1、前面抛出异常:新promise状态就该为rejected,结果为error
              reject(error)
            }
          })
          break

        // 状态为rejected的时候异步调用onRejected函数
        case REJECTED:
          setTimeout(() => {
            // 以下代码跟上面同理
            try {
              const result = onRejected(this.data) // 只修改了这里
              if (result instanceof Promise) {
                result.then(resolve, reject)
              } else {
                resolve(result)
              }
            } catch (error) {
              reject(error)
            }
          })
          break
      }
    }
}

提取出根据实例化结果的不同情况怎样进一步处理的函数handle

return new Promise((resolve, reject) => {
  const handle = callback => {
    try {
      const result = callback(this.data)
      if (result instanceof Promise) {
        result.then(resolve, reject)
      } else {
        resolve(result)
      }
    } catch (error) {
      reject(error)
    }
  }
  switch(this.status) {
    ...

    case RESOLVED:
      setTimeout(() => {
        handle(onResovled)
      })
      break

    case REJECTED:
      setTimeout(() => {
        handle(onRejected)
      })
      break
  }
})

考虑pending状态

  • 对保存起来的回调函数在上面的resolve和reject中遍历的调用后
  • 再调用handle函数做进一步的处理,主要还是要改变promsie的状态和传递结果
  • 且上面已经异步调用了,所以这里就不用异步了,直接同步调用即可
switch(this.status) {
    case PENDING:
      this.callbacks.push({
        onResovled() {
          handle(onResovled)
        },
        onRejected() {
          handle(onRejected)
        }
      })
      break
    ...
}

上面之所以把onResovledonRejected又包装了一层handle,就是因为不仅要调用这两个方法,而且还要修改promise的状态和传递结果。所以不仅要保存两个回调函数,而且还需要调用,而且看调用后的结果等等。

考虑异常传透

要考虑在没有传递第二个回调函数onRejected的时候,处理异常传透,主要是需要把原因抛出去,传递给后面捕获的地方

Promise.prototype.then = function (onResovled, onRejected) {
    onRejected = typeof onRejected === 'function' 
    ? onRejected 
    : reason => { throw reason }
    // 随便把onResovled也处理一下,如果传递的不是函数的话,
    // 把结果不做任何处理而是直接往下继续传递
    onResovled = typeof onResovled === 'function' 
    ? onResovled 
    : value => value
    ...
}

实现catch方法

Promise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected)
}

测试刚才的代码

// usePromise.js文件
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve('成功的结果!')
    reject('被拒绝的结果!')
  }, 1000)
}).then(
  value => {
    console.log('onResolved1: ' + value);
    // return 1
    // throw 'no'
    // return new Promise((resolve, reject) => reject(1))
  },
  reason => {
    console.log('onRejected1: ' + reason);
    // return 1
    // throw 'no'
    return new Promise((resolve, reject) => reject(1))
  }
).then(
  value => {
    console.log('onResolved2: ' + value);
  },
  reason => {
    console.log('onRejected2: ' + reason);
  }
).catch(
  reason => {
    console.log('onRejected3: ' + reason);
  }
)

实现resolve方法

  • 根据传递参数的不同,promise的状态也会不同
    • 传递非promise类型值
    • 传递调用resolvepromsie类型值
    • 传递调用rejectpromsie类型值
Promise.resolve = function (value) {
    return new Promise((resolve, reject) => {
      // 根据value的值不同,修改状态
      if (value instanceof Promise) {
        value.then(resolve, reject) // promise类型值
      } else {
        resolve(result) // 非promise类型值
      }
    })
}

测试实验原生Promise的结果

const p1 = Promise.resolve(1)
const p2 = Promise.resolve(Promise.resolve(2))
const p3 = Promise.resolve(Promise.reject(3))
p1.then(value => {console.log('p1: ' + value);})
p2.then(value => {console.log('p2: ' + value);})
p3.catch(reason => {console.log('p3: ' + reason);})
// 打印结果:
p1: 1
p2: 2
p3: 3

实现reject方法

Promise.reject = function (reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
}

实现all方法

Promise.all = function (promises) {
  // 声明用来保存成功的多个promise的数组,数组长度就按照传递的数组的长度设置
  const resolvedPromises = new Array(promises.length)
  // 用来统计成功的promise的数量的变量
  let resolvedCount = 0
  return new Promise((resolve, reject) => {
    promises.forEach((p, index) => {
      // p.then(
      // 考虑到传递进来的数组的每个元素不一定都是promise,所以需要包一层promise
      Promise.resolve(p).then(
        value => {
          resolvedCount++
          // 不能用push,因为push追加的顺序是按照成功先后的顺序
          // resolvedCount.push(value)
          // 应该按照传递进来的数组的顺序来放置
          resolvedPromises[index] = value
          // 只有全部都成功了,结果的promise的状态才能改为成功
          if (resolvedCount === promises.length) {
            resolve(resolvedPromises)
          }
        },
        reason => { // 只要有一个被拒绝了,结果就是被拒绝的状态
          reject(reason)
        }
      )
    })
  })
}

测试all方法

const p1 = Promise.resolve(1)
const p2 = Promise.resolve(Promise.resolve(2))
const p3 = Promise.resolve(Promise.reject(3))
const p4 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(4)
  })
})


const pAll = Promise.all([6, p4, p1, p2])
pAll.then(
  values => {
    console.log('onResolved: ', values);
  },
  reason => {
    console.log('onRejected: ', reason);
  }
)

实现race方法

Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    promises.forEach((p) => {
      // p.then(
      // 考虑到传递进来的数组的每个元素不一定都是promise,所以需要包一层promise
      Promise.resolve(p).then(
        value => { // 只要有成功就立即改变状态为成功
          resolve(value)
        },
        reason => { // 只要有被拒绝就立即改变状态为被拒绝
          reject(reason)
        }
      )
    })
  })
}

测试race方法

const p1 = Promise.resolve(Promise.resolve(1))
const p2 = Promise.resolve(Promise.resolve(2))
const p3 = Promise.reject(3)
const p4 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(4)
  })
})
const p5 = Promise.resolve(5)

const pRace = Promise.race([6, p5, p4, p3, p2, p1])
pRace.then(
  value => {
    console.log('onResolved: ', value);
  },
  reason => {
    console.log('onRejected: ', reason);
  }
)

增加延时处理的两个方法

实现resolveDelay

Promise.resolveDelay = function (value, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (value instanceof Promise) {
        value.then(resolve, reject)
      } else {
        resolve(value)
      }
    }, time)
  })
}

实现rejectDelay

Promise.rejectDelay = function (reason, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(reason)
    }, time)
  })
}

测试代码

const p1 = Promise.resolveDelay(1, 2000)
const p2 = Promise.rejectDelay(2, 3000)
p1.then(value => console.log('p1:' + value))
p2.catch(reason => console.log('p2:' + reason))

最终简洁版

(function (obj) {
  const PENDING = 'pending'
  const RESOLVED = 'resolved'
  const REJECTED = 'rejected'
  function Promise(excutor) {
    this.status = PENDING
    this.data = undefined
    this.callbacks = []
    const resolve = value => {
      if (this.status !== PENDING) return
      this.status = RESOLVED
      this.data = value
      setTimeout(() => {
        this.callbacks.forEach(obj => {
            obj.onResovled(this.data)
        })
      })
    }
    const reject = reason =>  {
      if (this.status !== PENDING) return
      this.status = REJECTED
      this.data = reason
      setTimeout(() => {
        this.callbacks.forEach(obj => {
          obj.onRejected(this.data)
        })
      })
    }
    try {
      excutor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  Promise.prototype.then = function (onResovled, onRejected) {
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    onResovled = typeof onResovled === 'function' ? onResovled : value => value
    return new Promise((resolve, reject) => {
      const handle = callback => {
        try {
          const result = callback(this.data)
          if (result instanceof Promise) {
            result.then(resolve, reject)
          } else {
            resolve(result)
          }
        } catch (error) {
          reject(error)
        }
      }
      switch(this.status) {
        case PENDING:
          this.callbacks.push({
            onResovled() {
              handle(onResovled)
            },
            onRejected() {
              handle(onRejected)
            }
          })
          break
        case RESOLVED:
          setTimeout(() => {
            handle(onResovled)
          })
          break
        case REJECTED:
          setTimeout(() => {
            handle(onRejected)
          })
          break
      }
    })
  }

  Promise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected)
  }

  Promise.resolve = function (value) {
    return new Promise((resolve, reject) => {
      if (value instanceof Promise) {
        value.then(resolve, reject) // promise类型值
      } else {
        resolve(value) // 非promise类型值
      }
    })
  }

  Promise.reject = function (reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }

  Promise.all = function (promises) {
    const resolvedPromises = new Array(promises.length)
    let resolvedCount = 0
    return new Promise((resolve, reject) => {
      promises.forEach((p, index) => {
        Promise.resolve(p).then(
          value => {
            resolvedCount++
            resolvedPromises[index] = value
            if (resolvedCount === promises.length) {
              resolve(resolvedPromises)
            }
          },
          reason => {
            reject(reason)
          }
        )
      })
    })
  }

  Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((p) => {
        Promise.resolve(p).then(
          value => {
            resolve(value)
          },
          reason => {
            reject(reason)
          }
        )
      })
    })
  }
  
  Promise.resolveDelay = function (value, time) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (value instanceof Promise) {
          value.then(resolve, reject)
        } else {
          resolve(value)
        }
      }, time)
    })
  }

  Promise.rejectDelay = function (reason, time) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(reason)
      }, time)
    })
  }

  obj.Promise = Promise
})(window)

class版本+测试代码