详解 Promise —— 手撕源码

1,398 阅读7分钟

一、前言

Promise 作为前端开发必不可少的工具,频频出现在面试题中,本文将会带领读者实现一个Promise对象,帮助读者从底层理解其构成。学后记得结合一些经典的面试题来巩固一下奥👊~

二、源码手撕

2.1 基本定义

需求:
1.定义 Promise 对象,在构造函数中定义 resolvereject 方法
2.在构造函数中定义 state 参数,初始值为 ‘pending’,同时设有 ‘fulfiled’‘rejected’ 共三种状态
3.在构造函数中定义 successfail 参数,分别对应传入 resolvereject 的值

注意:
1.Promisestate 只能改变一次
2.应该使用 try catch 语法完善 executor(resolve,reject) 的执行

综上,写出的代码如下:

class MyPromise {
  constructor(executor) {
    this.state = 'pending' // 记录Promise对象的状态
    this.success = null // 记录成功的值 用then调用
    this.fail = null // 记录失败的值 用catch调用

    const reslove = (success) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.success = success
      }
    }

    const reject = (fail) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.fail = fail
      }
    }
    
    // 如果执行器中出现错误 直接将promise改为失败状态
    try {
      executor(reslove, reject)
    } catch (error) {
      reject(error)
    }
  }
}

const p = new MyPromise((reslove, reject) => {
  reslove(6)
})

p.then((res) => {
  console.log(res)
})

当然目前的代码还无法运行,接下来让我们完善 then()的调用

2.2 then()、异步操作

首先解决 then() 的问题
需求:
1.传入 onFulfiledonRejected 方法
2.用 onFulfiledonRejected 接收 p.then() 传入的 successfail

then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') onFulfilled(this.success)
    if (this.state === 'rejected') onRejected(this.fail)
  }

与此同时,还需要加入异步操作,才能保证 then 执行时,state 不为‘pending’状态(reslove可能被异步执行)

需求:
1.then() 的执行不直接输出结果,而是将得到的数据先全部存放在两个数组中(成功和失败),在 resolvereject 执行后再将数组中的函数输出
2.需要增加一个 if 判断 state 如果是 pending,将数据存储在数组中
3.在 resolvereject 方法中遍历两数组得到数据

注意: (这个真的很重要!!!🙉🙉🙉)
①函数在数组中的存储需要用到函数的闭包(在函数的内部应用到外部的参数)——利用闭包将 successfail 储存在函数中,并整合至数组

综上,代码如下:

class MyPromise {
  constructor(executor) {
    this.state = 'pending' // 记录Promise对象的状态
    this.success = null // 记录成功的值 用then调用
    this.fail = null // 记录失败的值 用catch调用

    // 记录成功、失败的回调函数数组(通过闭包存储了success、fail值)
    this.resloveCallbacks = []
    this.rejectCallbacks = []

    const resolve = (success) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.success = success
        // 调用记录的回调函数
        while (this.resolveCallbacks.length) {
          this.resolveCallbacks.shift()(value)
        }
      }
    }

    const reject = (fail) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.fail = fail
        // 调用记录的回调函数
        while (this.rejectCallbacks.length) {
          this.rejectCallbacks.shift()(value)
        }
      }
    }
    
    // 如果执行器中出现错误 直接将promise改为失败状态
    try {
      executor(reslove, reject)
    } catch (error) {
      reject(error)
    }
  }
  then(onFulfilled, onRejected) {
    if (this.state === 'fulfilled') onFulfilled(this.success)
    if (this.state === 'rejected') onRejected(this.fail)
    if (this.state === 'pending') {
      this.resloveCallbacks.push(() => {
        onFulfilled(this.success)
      })
      this.rejectCallbacks.push(() => {
        onRejected(this.fail)
      })
    }
  }

}

const p = new MyPromise((reslove, reject) => {
  setTimeout(() => {
    reslove(6)
  }, 500)
})

p.then((res) => {
  console.log(res)
})
// 输出:0.5s后打印 6

2.2 链式调用

接下来就是最关键的链式调用部分
需求:
1.上一个 .then 要返回一个 Promise 对象
2.下一个 .then 的参数,要拿到上一个 .then 的回调返回值
3.设置参数 itemonFulfilled 成功的 .then 回调的返回值,并通过 resolve(item) 来进行链式操作
4.参照 fulfilled 的处理方式,改造 rejectedpending
5.then() 可以不传参数

注意:
1.代码运用了递归,上一级 then 返回 Promsie 对象来延续下一级的 then,因此需要在 then 函数中构造一个新的 Promise 对象并返回,在新的 Promise 对象中通过当前阶段的回调函数值决定下一步操作
2.item 可能是传入的普通值,也可能是 Promise 对象,因此要判断其类型并进行相应的处理
3.为避免循环调用(情景如下代码),通过判断语句(item === promiseNext ?)来解决以上矛盾,然而判断逻辑写在 promiseNext 内部,此时 promiseNext 未完成初始化,因此可以借助宏任务微任务,将数据传入一个函数中进行判断,在此我们使用微任务 queueMicrotask 进行处理(当然也可以使用setTimeout 0ms 处理)

const p2 = p.then((data)=>{
            console.log(data);
            return p2
        })

综上,代码如下:

class MyPromise {
  constructor(executor) {
    this.state = 'pending' // 记录Promise对象的状态
    this.success = null // 记录成功的值 用then调用
    this.fail = null // 记录失败的值 用catch调用

    // 记录成功、失败的回调函数数组(通过闭包存储了success、fail值)
    this.resolveCallbacks = []
    this.rejectCallbacks = []

    const resolve = (success) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.success = success
        // 调用记录的回调函数
        while (this.resolveCallbacks.length) {
          this.resolveCallbacks.shift()(value)
        }
      }
    }

    const reject = (fail) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.fail = fail
        // 调用记录的回调函数
        while (this.rejectCallbacks.length) {
          this.rejectCallbacks.shift()(value)
        }
      }
    }

    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
  then(onFulfilled, onRejected) {
    // 处理不传参数的情况 不传则使用默认值
    onFulfilled = (typeof onFulfilled === 'function') ? onFulfilled : success => success;
    onRejected = (typeof onRejected === 'function') ? onRejected : fail => { throw fail };
    
    let promiseNext = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 通过微任务确保 promiseNext 初始化完成
        // 使用try catch 返回error时可以捕获抛出
        queueMicrotask(() => {
          try {
            let item = onFulfilled(this.success)
            resolvePromise(item, resolve, reject, promiseNext)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.state === 'rejected') {
        queueMicrotask(() => {
          try {
            const item = onRejected(this.reason);
            // 传入 resolvePromise 集中处理
            resolvePromise(item, resolve, reject, promiseNext)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.state === 'pending') {
        this.resolveCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              let item = onFulfilled(this.success)
              resolvePromise(item, resolve, reject, promiseNext)
            } catch (error) {
              reject(error)
            }
          })
        })
        this.rejectCallbacks.push(() => {
          try {
            const item = onRejected(this.reason);
            // 传入 resolvePromise 集中处理
            resolvePromise(item, resolve, reject, promiseNext)
          } catch (error) {
            reject(error)
          }
        })
      }
    })

    return promiseNext
  }

}

const resolvePromise = (x, resolve, reject, promiseNext) => {
  //处理循环调用
  if (x === promiseNext) {
    const err = new TypeError('Uncaught (in promsie) TypeError:detected for promise #<Promsie>')
    console.error(err)
    return reject(err)
  }
  //判断 x 是否是 MyPromise 对象
  if (x instanceof MyPromise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}

const p = new MyPromise((reslove, reject) => {
  reslove('p')
})

const q = new MyPromise((reslove, reject) => {
  reslove('q')
})

p.then((res) => {
  console.log(res)
  return q
}).then((res) => {
  console.log(res)
})
// 输出:
// p 
// q

const p2 = p.then((data) => {
  console.log(data);
  return p2
})
// 输出:
// p
// TypeError: Uncaught (in promsie) TypeError:detected for promise #<Promsie>

2.3 实现 resolve 和 reject

比较简单,直接上代码

  // resolve 静态方法
  static resolve(success) {
    if (success instanceof MyPromise) {
      return success;
    }

    return new MyPromise(resolve => {
      resolve(success);
    });
  }

  // reject 静态方法
  static reject(fail) {
    if (fail instanceof MyPromise) {
      return fail;
    }
    return new MyPromise((resolve, reject) => {
      reject(fail);
    });
  }

2.4 实现剩余方法

1.finally:无论传入的是 resolve 还是 reject 都会输出 (一般用于Promise出错后的善后措施,如某些计时器的关闭等)

  static finally(fn) {
    return this.then(
      (success) => {
        fn()
        return success
      },
      (fail) => {
        fn()
        throw fail
      });
  };

2.catch: 抛出错误

static catch(onRejected) {
    return this.then(null, onRejected)
  }

3.all:接受一个 Promise 数组,当所有 Promise 状态 resolve 后,执行 resolve

static all(promises) {
    return new MyPromise((resolve, reject) => {
      if (promises.length === 0) {
        resolve([])
      } else {
        let result = []
        let index = 0;
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(
            (data) => {
              result[i] = data
              if (++index === promises.length) {
                resolve(result)
              }
            },
            (err) => {
              reject(err)
              return
            })
        }
      }
    })
  }

4.race:接受一个 promise 数组,当有一个 promise 状态 resolve 后,执行resolve(谁快谁先执行)

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      if (promises.length === 0) {
        resolve()
      } else {
        let index = 0
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(
            (data) => {
              resolve(data)
            },
            (err) => {
              reject(err)
              return
            })
        }
      }
    })
  }

至此,Promise已经大功告成,以下是完整的代码:

class MyPromise {
  constructor(executor) {
    this.state = 'pending' // 记录Promise对象的状态
    this.success = null // 记录成功的值 用then调用
    this.fail = null // 记录失败的值 用catch调用

    // 记录成功、失败的回调函数数组(通过闭包存储了success、fail值)
    this.resolveCallbacks = []
    this.rejectCallbacks = []

    const resolve = (success) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.success = success
        // 调用记录的回调函数
        while (this.resolveCallbacks.length) {
          this.resolveCallbacks.shift()(success)
        }
      }
    }

    const reject = (fail) => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.fail = fail
        // 调用记录的回调函数
        while (this.rejectCallbacks.length) {
          this.rejectCallbacks.shift()(fail)
        }
      }
    }

    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    // 处理不传参数的情况 不传则使用默认值
    onFulfilled = (typeof onFulfilled === 'function') ? onFulfilled : success => success;
    onRejected = (typeof onRejected === 'function') ? onRejected : fail => { throw fail };

    let promiseNext = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 通过微任务确保 promiseNext 初始化完成
        // 使用try catch 返回error时可以捕获抛出
        queueMicrotask(() => {
          try {
            let item = onFulfilled(this.success)
            resolvePromise(item, resolve, reject, promiseNext)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.state === 'rejected') {
        queueMicrotask(() => {
          try {
            const item = onRejected(this.reason);
            // 传入 resolvePromise 集中处理
            resolvePromise(item, resolve, reject, promiseNext)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.state === 'pending') {
        this.resolveCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              let item = onFulfilled(this.success)
              resolvePromise(item, resolve, reject, promiseNext)
            } catch (error) {
              reject(error)
            }
          })
        })
        this.rejectCallbacks.push(() => {
          try {
            const item = onRejected(this.reason);
            // 传入 resolvePromise 集中处理
            resolvePromise(item, resolve, reject, promiseNext)
          } catch (error) {
            reject(error)
          }
        })
      }
    })

    return promiseNext
  }

  // resolve 静态方法
  static resolve(success) {
    if (success instanceof MyPromise) {
      return success;
    }

    return new MyPromise(resolve => {
      resolve(success);
    });
  }

  // reject 静态方法
  static reject(fail) {
    if (fail instanceof MyPromise) {
      return fail;
    }
    return new MyPromise((resolve, reject) => {
      reject(fail);
    });
  }

  static finally(fn) {
    return this.then(
      (success) => {
        fn()
        return success
      },
      (fail) => {
        fn()
        throw fail
      });
  }

  static catch(onRejected) {
    return this.then(null, onRejected)
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      if (promises.length === 0) {
        resolve([])
      } else {
        let result = []
        let index = 0;
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(
            (data) => {
              result[i] = data
              if (++index === promises.length) {
                resolve(result)
              }
            },
            (err) => {
              reject(err)
              return
            })
        }
      }
    })
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      if (promises.length === 0) {
        resolve()
      } else {
        let index = 0
        for (let i = 0; i < promises.length; i++) {
          promises[i].then(
            (data) => {
              resolve(data)
            },
            (err) => {
              reject(err)
              return
            })
        }
      }
    })
  }

}



const resolvePromise = (x, resolve, reject, promiseNext) => {
  //处理循环调用
  if (x === promiseNext) {
    const err = new TypeError('Uncaught (in promsie) TypeError:detected for promise #<Promsie>')
    console.error(err)
    return reject(err)
  }
  //判断 x 是否是 MyPromise 对象
  if (x instanceof MyPromise) {
    x.then(resolve, reject)
  } else {
    resolve(x)
  }
}

const p = new MyPromise((reslove, reject) => {
  reslove('p')
})

const q = new MyPromise((reslove, reject) => {
  reslove('q')
})

p.then((res) => {
  console.log(res)
  return q
}).then((res) => {
  console.log(res)
})
// 输出:
// p 
// q

三、总结

读者们其实可以用一些 Promise 的验证工具来验证自己的 Promise 对象是否合规(如PromiseA+,详见百度~),相信在走完一遍流程之后,大家对 Promise 的认知会更进一步,本文中存在一些有误的信息也欢迎读者指出,本菜鸟会速速修正的!