Bootstrap

手撕 Promise(then 链及 Promise 方法实现)

Promise 类基础逻辑

Promise 可以解决的问题

  1. 解决回调地狱问题,不会导致难以维护
  2. 合并多个异步请求,节约时间

基础逻辑

  1. new Promise:Promise 是一个类,在执行这个类的时候,需要传递一个执行器进去,执行器会立即执行

  2. Promise 中有三种状态,分别为:成功(fulfilled)、失败(rejected)、等待(pending),一旦状态确定就不可更改

    如果状态不是等待,需要阻止程序向下执行

  3. resolve 和 reject 函数是用来更改状态的

    把 resolve 和 reject 定义为箭头函数是为了让函数内部的 this 指向,指向类的实例对象(Promise)

  4. then 方法内部做的事情就是判断状态,如果状态是成功,就调用成功的回调函数,如果状态是失败,就调用失败的回调函数。then 方法定义在原型对象上的

  5. then 成功回调有一个参数,表示成功之后的值,then 失败回调有一个参数,表示失败的原因

// 可复用且有提示
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功
const REJECTED = 'rejected' // 失败
class MyPromise {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  // promise 状态
  status = PENDING
  // 成功之后的值
  value = undefined
  // 失败之后的原因
  reason = undefined
  resolve = value => {
    // 如果状态不是等待,阻止程序向下执行
    if (this.status !== PENDING) return
    // 将状态更改为成功
    this.status = FULFILLED
    // 保存成功之后的值
    this.value = value
  }
  reject = reason => {
    // 如果状态不是等待,阻止程序向下执行
    if (this.status !== PENDING) return
    // 将状态更改为失败
    this.status = REJECTED
    // 保存失败之后的原因
    this.reason = reason
  }
  then(successCallback, failCallback) {
    // 判断状态
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    }
  }
}

module.exports = MyPromise

之后对其进行验证

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {
  resolve('成功')
  reject('失败')
})
promise.then(
  value => {
    console.log(value)
  },
  reason => {
    console.log(reason)
  }
)

加入 then 方法和异步逻辑

问题1: 如果要给 resolve 或 reject 包裹一层 setTimeout,发现什么都没有输出

  • 主线程是不会等待异步 setTimeout 执行完成的,then 会立即执行
  • 由于当前 Promise 执行到 then 时状态为 等待态,而现在只判断成功与失败的状态,所以会什么都不输出
let promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功')
  }, 2000)
})
promise.then(value => {
  console.log(value) // 没有输出
})

问题2: 当 then 方法被多次调用时,每一个 then 方法中传递的回调函数都是要执行的

  • 同步:如果调用 then 方法时,已经知道 promise 状态为 成功态或失败态,就可以直接调用回调即可
  • 异步:如果调用 then 方法时,promise 状态为 等待态,每一个 then 方法的回调函数都应该存储起来,当状态为成功或失败时,再依次调用回调函数
let promise = new MyPromise((resolve, reject) => {
  resolve('成功')
})
promise.then(value => {
  console.log(value) // 成功
})
promise.then(value => {
  console.log(value) // 没有输出
})

接下来处理上面出现的两个问题

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
  constructor(executor) {
    executor(this.resolve, this.reject)
  }
  status = PENDING
  value = undefined
  reason = undefined
  // 成功回调 undefined -> []
  successCallback = []
  // 失败回调 undefined -> []
  failCallback = []
  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    // 判断成功回调是否存在,如果存在调用
    // this.successCallback && this.successCallback(this.value)
    while (this.successCallback.length) this.successCallback.shift()(this.value)
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    // 判断失败回调是否存在,如果存在调用
    // this.failCallback && this.failCallback(this.value)
    while (this.failCallback.length) this.failCallback.shift()(this.reason)
  }
  then(successCallback, failCallback) {
    if (this.status === FULFILLED) {
      successCallback(this.value)
    } else if (this.status === REJECTED) {
      failCallback(this.reason)
    } else {
      // 将成功回调和失败回调存储起来
      this.successCallback.push(successCallback)
      this.failCallback.push(failCallback)
    }
  }
}

module.exports = MyPromise

实现 then 链

链式调用

then 方法是可以被链式调用的,后面 then 方法的回调函数拿到上一个 then 方法的回调函数的返回值

let promise = new MyPromise((resolve, reject) => {
  resolve('成功')
})
promise.then(value => {
  console.log(value) // 成功
  return 100
}).then(value => {
  console.log(value) // 没有输出
})

接下来需要实现如下需求

  • 实现 then 方法链式调用(then 方法返回一个 promise)

  • 如何把上一个 then 回调函数的返回值传递给下一个 then 方法的回调函数

    这里需要判断回调函数返回值是普通值还是 Promise 对象

    • 如果是普通值,直接调用 resolve
    • 如果是 promise 对象,查看 promise 对象返回的结果,根据结果决定调用 resolve 还是 reject
class MyPromise {
  then(successCallback, failCallback) {
    // 返回一个新的 Promise
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        let x = successCallback(this.value)
        // 判断 x 的是普通值还是 promise 对象
        resolvePromise(x, resolve, reject)
      } else if (this.status === REJECTED) {
        failCallback(this.reason)
      } else {
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
      }
    })
    return promise2
  }
}

function resolvePromise(x, resolve, reject) {
  if (x instanceof MyPromise) {
    // promise 对象
    /* x.then(
      value => resolve(value),
      reason => reject(reason)
    ) */
    x.then(resolve, reject)
  } else {
    // 普通值
    resolve(x)
  }
}

循环引用

如果在 p1 里返回 p1 这个 Promise,就会发生循环调用

let promise = new MyPromise((resolve, reject) => {
  resolve('成功')
})
let p1 = promise.then(value => {
  console.log(value)
  return p1
})

p1.then(
  value => {
    console.log(value)
  },
  reason => {
    console.log(reason) // TypeError: Chaining cycle detected for promise #<Promise>
  }
)

注意: 举个例子 var obj = { n: 10, x: obj.n *10 },因为 obj 还没有创建完,而在创建属性 x 时是获取不到 obj.n 的。因为全局作用于只声明了 obj,却没有赋值(obj -> undefiend),promise 也是有这样的情况的,我们可以使用异步任务,让其赋值完成

class MyPromise {
  then(successCallback, failCallback) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        // 变为异步任务这样 promise2 就赋值完成了
        setTimeout(() => {
          let x = successCallback(this.value)
          resolvePromise(promise2, x, resolve, reject)
        }, 0)
      } else if (this.status === REJECTED) {
        failCallback(this.reason)
      } else {
        this.successCallback.push(successCallback)
        this.failCallback.push(failCallback)
      }
    })
    return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}

捕获错误

  1. 当执行器(executor)中代码执行时发生错误,将 promise 状态改为失败态

  2. 回调函数在执行时发生错误,这个错误要在下个 then 方法的回调函数中捕获到

    当代码为失败状态或等待态也需要用 try...catch 包裹

  3. 当代码为等待态时,如果碰到异步,不能把回调函数直接 push 到数组里,这样没有办法对其进行处理,我们可以 push 一个函数进去,函数里面调用成功或失败回调

    这时就可以对进行异步和错误捕获处理了

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
  constructor(executor) {
    // 捕获执行器错误
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }
  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []
  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()()
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()()
  }
  then(successCallback, failCallback) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          // 捕获回调函数发生的错误
          try {
            let x = successCallback(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = failCallback(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else {
        // push 函数进去,函数里面调用成功回调函数
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let x = successCallback(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        // push 函数进去,函数里面调用失败回调函数
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let x = failCallback(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    return promise2
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}

module.exports = MyPromise

then 方法参数变为可选

链式调用 then 时,即使不传递参数也会依次向后传递

const promise = new MyPromise((resolve, reject) => {
  resolve(100)
})
promise
  .then()
  .then()
  .then(value => console.log(value)) // 100

其实就是判断 successCallbackfailCallback 是否存在,如果不存在给它进行赋值

class MyPromise {
  then(successCallback, failCallback) {
    successCallback = successCallback ? successCallback : value => value
    failCallback = failCallback
      ? failCallback
      : reason => {
          throw reason
        }
  }
}

其他方法实现

Promise.all

  • all 方法是通过类直接调用,所以 all 方法是一个静态方法
  • all 传入的必须是一个数组,如果数组中某一项不是 Promise 实例,需要把它转换为成功的 Promise 实例
  • 之后每一项依次执行,只要有一项失败返回就是失败的,必须全部成功才是成功
function p1() {
  return new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolve('p1')
    }, 2000)
  })
}
function p2() {
  return new MyPromise((resolve, reject) => {
    resolve('p2')
  })
}

MyPromise.all(['a', 'b', p1(), p2(), 'c']).then(result => console.log(result)) // [ 'a', 'b', 'p1', 'p2', 'c' ]

注意:

  • 返回结果的顺序跟传入的顺序一致(不能使用 push,因为不能确定谁先到,需要使用索引)
  • for 循环执行就是一瞬间的,但是里面可能存在异步操作,需要等待所有都执行完,再执行 resolve 操作
class MyPromise {
  static all(array) {
    let result = []
    let index = 0
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value
        index++
        if (index === array.length) {
          resolve(result)
        }
      }
      for (let i = 0; i < array.length; i++) {
        const current = array[i]
        if (current instanceof MyPromise) {
          // promise 对象
          current.then(
            value => addData(i, value),
            reason => reject(reason)
          )
        } else {
          // 普通值
          addData(i, array[i])
        }
      }
    })
  }
}

Promise.resolve

function p1() {
  return new MyPromise((resolve, reject) => {
    resolve('p1')
  })
}
Promise.resolve(10).then(value => console.log(value))
Promise.resolve(p1()).then(value => console.log(value))
  • 如果参数是 Promise 实例,那么 Promise.resolve 将不做任何修改,原封不动地返回这个实例
  • 有如果参数是普通值,就直接将值转换为成功的 Promise 实例
class MyPromise {
  static resolve(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }
}

finally

function p1() {
  return new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolve('p1')
    }, 2000)
  })
}
function p2() {
  return new MyPromise((resolve, reject) => {
    resolve('p2')
  })
}

p2()
  .finally(() => {
    console.log('finally')
    return p1()
  })
  .then(
    value => {
      console.log(value)
    },
    reason => {
      console.log(reason)
    }
  )
  • 无论当前 Promise 最终状态是成功的还是失败的,finally 中的回调函数始终都会被执行一次
  • 在 finally 方法后面链式调用 then 方法拿到当前这个 promise 最终返回的结果
class MyPromise {
  finally(callback) {
    return this.then(
      value => {
        return MyPromise.resolve(callback()).then(() => value)
      },
      reason => {
        return MyPromise.resolve(callback()).then(() => {
          throw reason()
        })
      }
    )
  }
}

catch

  • 处理当前 promise 为失败状态,内部调用的也是 then 方法(只注册失败回调)
class MyPromise {
  catch(failCallback) {
    return this.then(undefined, failCallback)
  }
 }

完整版

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
  constructor(executor) {
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }
  status = PENDING
  value = undefined
  reason = undefined
  successCallback = []
  failCallback = []
  resolve = value => {
    if (this.status !== PENDING) return
    this.status = FULFILLED
    this.value = value
    while (this.successCallback.length) this.successCallback.shift()()
  }
  reject = reason => {
    if (this.status !== PENDING) return
    this.status = REJECTED
    this.reason = reason
    while (this.failCallback.length) this.failCallback.shift()()
  }
  then(successCallback, failCallback) {
    successCallback = successCallback ? successCallback : value => value
    failCallback = failCallback
      ? failCallback
      : reason => {
          throw reason
        }
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = successCallback(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = failCallback(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else {
        this.successCallback.push(() => {
          setTimeout(() => {
            try {
              let x = successCallback(this.value)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        this.failCallback.push(() => {
          setTimeout(() => {
            try {
              let x = failCallback(this.reason)
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    return promise2
  }
  finally(callback) {
    return this.then(
      value => {
        return MyPromise.resolve(callback()).then(() => value)
      },
      reason => {
        return MyPromise.resolve(callback()).then(() => {
          throw reason()
        })
      }
    )
  }
  catch(failCallback) {
    return this.then(undefined, failCallback)
  }
  static all(array) {
    let result = []
    let index = 0
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value
        index++
        if (index === array.length) {
          resolve(result)
        }
      }
      for (let i = 0; i < array.length; i++) {
        const current = array[i]
        if (current instanceof MyPromise) {
          current.then(
            value => addData(i, value),
            reason => reject(reason)
          )
        } else {
          addData(i, array[i])
        }
      }
    })
  }
  static resolve(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  if (x instanceof MyPromise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}

module.exports = MyPromise
;