详细解答Promise源码

写这篇文章的目的是解剖Promise源码,起因也是最近秋招被问到了让手写Promise,另外在网上看到的Promise源码或多或少有些小问题,也就是没有完全遵循Promise/A+规范。

代码会完全使用ES6语法,主要分以下几个模块:

  • 整体分析(为代码书写铺路)
  • 实现初版(构造函数大致功能亿完善)
  • 支持异步和链式调用(完善then方法)
  • 实现catch方法
  • 实现Promise.resolve()
  • 实现Promise.reject()
  • 实现Promise.all()
  • 实现Promise.race()

一、整体分析

所谓Promise就是一个容器,有三个状态:PENDING(进行中)、FULFILLED(成功)、REJECTED(失败),里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,有两大特点:

  • 容器状态不受外界影响
  • 一旦状态改变就不会再变,任何时候都可以得到这个结果

来看下Promise的用法:

new Promise((resolve, reject) => {
  // ...
  // 成功则执行resolve,否则指定reject
}).then(
  res => {
    // resolve对应触发函数的执行
  },
  err => {
    // reject对应触发函数的执行
  }
).then(
  // 支持链式调用
  res => {
  
  }
).catch(
  err => console.log(err)
)
Promise.resolve();
Promise.reject();
Promise.all([promise1, promise2, ...]).then();
Promise.race([promise1, promise2, ...]).then();
<span class="copy-code-btn">复制代码</span>

通过用法不难分析出:

  • Promise构造函数接受一个函数参数exector,exector接受resolvereject两个函数并立即执行,通过resolve/reject改变状态
  • 状态改变后,触发原型链上的then、catch方法
  • Promise类拥有静态方法resolve、reject、all、race

那么可以写出大致结构代码:

class Promise {
  constructor(exector) {
    const resolve = () => {

    }
    const reject = () => {

    }
    exector(resolve, reject);
  }
  then() {

  }
  catch() {

  }
  static resolve() {

  }
  static reject() {

  }
  static all() {

  }
  static race() {
    
  }
}
复制代码

之后在此基础上补充代码。

二、实现初版

首先引入三种状态,完善resolvereject函数,最后在构造函数内执行exector(resolve, reject)

// 定义三种状态
const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

class Promise {
  constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将成功、失败结果放在this上,便于then、catch访问
    this.value = undefined;
    this.reason = undefined;

    const resolve = value => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
      }
    }
    const reject = reason => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
      }
    }
    // 立即执行exector
    // 把内部的resolve和reject传入executor,用户可调用resolve和reject
    exector(resolve, reject);
  }
}
复制代码

注意:exector(resolve, reject);执行可能会报错,所以需要使用try包括一下,有报错reject抛出去。

constructor(exector) {
  // 初始化状态
  this.status = PENDING;
  // 将成功、失败结果放在this上,便于then、catch访问
  this.value = undefined;
  this.reason = undefined;

  const resolve = value => {
    if (this.status === PENDING) {
      // 只有进行中状态才能更改状态
      this.status = FULFILLED;
      this.value = value;
    }
  }
  const reject = reason => {
    if (this.status === PENDING) {
      // 只有进行中状态才能更改状态
      this.status = REJECTED;
      this.reason = reason;
    }
  }
  // 修改代码
  try {
    // 立即执行executor
    // 把内部的resolve和reject传入executor,用户可调用resolve和reject
    exector(resolve, reject);
  } catch(e) {
    // executor执行出错,将错误内容reject抛出去
    reject(e);
  }
}
复制代码

此时可以使用then进行捕获了,then接收两个函数,分别对应FULFILLEDREJECTED状态:

new Promise().then(
  res => {},
  err => {},
)
<span class="copy-code-btn">复制代码</span>

注意:thencatch是微任务,这里使用setTimeout模拟:

then(onFulfilled, onRejected) {
  // then是微任务,这里用setTimeout模拟
  setTimeout(() => {
    if (this.status === FULFILLED) {
      // FULFILLED状态下才执行
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // REJECTED状态下才执行
      onRejected(this.reason);
    }
  })
}
复制代码

OK,初版已经完成:

// 定义三种状态
const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

class Promise {
  constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将成功、失败结果放在this上,便于then、catch访问
    this.value = undefined;
    this.reason = undefined;

    const resolve = value => {
      if (this.status === PENDING) {
        // 只有进行中状态才能更改状态
        this.status = FULFILLED;
        this.value = value;
      }
    }
    const reject = reason => {
      if (this.status === PENDING) {
        // 只有进行中状态才能更改状态
        this.status = REJECTED;
        this.reason = reason;
      }
    }
    try {
      // 立即执行executor
      // 把内部的resolve和reject传入executor,用户可调用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor执行出错,将错误内容reject抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    // then是微任务,这里用setTimeout模拟
    setTimeout(() => {
      if (this.status === FULFILLED) {
        // FULFILLED状态下才执行
        onFulfilled(this.value);
      } else if (this.status === REJECTED) {
        // REJECTED状态下才执行
        onRejected(this.reason);
      }
    })
  }
}
复制代码

可以拿数据测试一下:

const promise = new Promise((resolve, reject) => {
  Math.random() < 0.5 ? resolve(1) : reject(-1);
}).then(
  res => console.log(res),
  err => console.log(err),
)
复制代码

三、支持异步和链式调用

此时初版还有三个方向需要完善:

  1. Promise内部异步代码执行的问题。
  2. Promise的链式调用
  3. 值传透

支持异步代码

开发中经常会将接口放于promise内部,等接口请求响应成功把数据resolve出去,或失败时把数据reject出去,此时thencatch才会进行捕获。

而现在的代码,promise内部如果有异步代码执行后才resolvethen不会等待异步代码执行完毕会直接执行,所以此时状态是PENDING,不会触发then的回调函数。

新增onFulfilledCallbacksonRejectedCallbacks维护成功态、失败态任务队列:

// 定义三种状态
const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

class Promise {
  constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将成功、失败结果放在this上,便于then、catch访问
    this.value = undefined;
    this.reason = undefined;
    
    // 新增代码:
    // 成功态回调函数队列
    this.onFulfilledCallbacks = [];
    // 失败态回调函数队列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 新增代码:
        // 成功态函数依次执行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 新增代码:
        // 失败态函数依次执行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 立即执行executor
      // 把内部的resolve和reject传入executor,用户可调用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor执行出错,将错误内容reject抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    // then是微任务,这里用setTimeout模拟
    setTimeout(() => {
      // 新增代码:
      if (this.status === PENDING) {
        // 状态是PENDING下执行
        // 说明promise内部有异步代码执行,还未改变状态,添加到成功/失败回调任务队列即可
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);
      }else if (this.status === FULFILLED) {
        // FULFILLED状态下才执行
        onFulfilled(this.value);
      } else if (this.status === REJECTED) {
        // REJECTED状态下才执行
        onRejected(this.reason);
      }
    })
  }
}

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
}).then(
  res => console.log(res)
)
// 1
复制代码

实现链式调用

Promise的一大优势就是支持链式调用,具体来说就是then方法的具体实现,实际上是返回了一个Promise,需要注意的几个点:

  1. 保存之前promise实例的引用,即保存this
  2. 根据then回调函数执行的返回值
  • 如果是promise实例,那么返回的下一个promise实例会等待这个promise状态发生变化
  • 如果不是promise实例,根据目前情况直接执行resolvereject

完善then函数:

then(onFulfilled, onRejected) {
  // 保存this
  const self = this;
  return new Promise((resolve, reject) => {
    if (self.status === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        // try捕获错误
        try {
          // 模拟微任务
          setTimeout(() => {
            const result = onFulfilled(self.value);
            // 分两种情况:
            // 1. 回调函数返回值是Promise,执行then操作
            // 2. 如果不是Promise,调用新Promise的resolve函数
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      });
      self.onRejectedCallbacks.push(() => {
        // 以下同理
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            // 不同点:此时是reject
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      })
    } else if (self.status === FULFILLED) {
      setTimeout(() => {
        try {
          const result = onFulfilled(self.value);
          result instanceof Promise ? result.then(resolve, reject) : resolve(result);
        } catch(e) {
          reject(e);
        }
      });
    } else if (self.status === REJECT){
      setTimeout(() => {
        try {
          const result = onRejected(self.error);
          result instanceof Promise ? result.then(resolve, reject) : resolve(result);
        } catch(e) {
          reject(e);
        }
      })
    }
  })
}
复制代码

值传透

Promise支持值穿透:

let promsie = new Promise((resolve,reject)=>{
  resolve(1)
})
.then(2)
.then(3)
.then(value => {
  console.log(value)
})
// 1
复制代码

then参数期望是函数,传入非函数则会发生值穿透。值传透可以理解为,当传入then的不是函数的时候,这个then是无效的。

原理上是当then中传入的不算函数,则这个promise返回上一个promise的值,这就是发生值穿透的原因,所以只需要对then的两个参数进行设置就行了:

onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected:
    reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
复制代码

完整的then函数代码:

then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function'? onRejected:
    reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
  // 保存this
  const self = this;
  return new Promise((resolve, reject) => {
    if (self.status === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        // try捕获错误
        try {
          // 模拟微任务
          setTimeout(() => {
            const result = onFulfilled(self.value);
            // 分两种情况:
            // 1. 回调函数返回值是Promise,执行then操作
            // 2. 如果不是Promise,调用新Promise的resolve函数
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      });
      self.onRejectedCallbacks.push(() => {
        // 以下同理
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            // 不同点:此时是reject
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      })
    } else if (self.status === FULFILLED) {
      try {
        setTimeout(() => {
          const result = onFulfilled(self.value);
          result instanceof Promise ? result.then(resolve, reject) : resolve(result);
        });
      } catch(e) {
        reject(e);
      }
    } else if (self.status === REJECTED){
      try {
        setTimeout(() => {
          const result = onRejected(self.reason);
          result instanceof Promise ? result.then(resolve, reject) : resolve(result);
        })
      } catch(e) {
        reject(e);
      }
    }
  });
}
复制代码

四、实现catch()方法

Promise.prototype.catch就是Promise.prototype.then(null, onRejected)的别名,所以实现就很简单了:

catch(onRejected) {
  return this.then(null, onRejected);
}
复制代码

五、Promise.resolve()

这里就不考虑参数是thenable对象了,那么参数有两种情况:

  1. Promise实例
  2. 不是Promise实例
static resolve(value) {
  if (value instanceof Promise) {
    // 如果是Promise实例,直接返回
    return value;
  } else {
    // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
    return new Promise((resolve, reject) => resolve(value));
  }
}
复制代码

六、Promise.reject()

Promise.reject也会返回一个Promise实例,状态为REJECTED

Promise.resolve不同的是,Promise.reject方法的参数会原封不动地作为reject的参数

static reject(reason) {
  return new Promise((resolve, reject) => {
    reject(reason);
  })
}
复制代码

七、Promise.all()

返回一个promise对象,只有当所有promise都成功时返回的promise状态才成功,需要注意的点是:

  1. 所有的promise状态变为FULFILLED,返回的promise状态才变为FULFILLED
  2. 一个promise状态变为REJECTED,返回的promise状态就变为REJECTED
  3. 数组成员不一定都是promise,需要使用Promise.resolve()处理。
static all(promiseArr) {
  const len = promiseArr.length;
  const values = new Array(len);
  // 记录已经成功执行的promise个数
  let count = 0;
  return new Promise((resolve, reject) => {
    for (let i = 0; i < len; i++) {
      // Promise.resolve()处理,确保每一个都是promise实例
      Promise.resolve(promiseArr[i]).then(
        val => {
          values[i] = val;
          count++;
          // 如果全部执行完,返回promise的状态就可以改变了
          if (count === len) resolve(values);
        },
        err => reject(err),
      );
    }
  })
}
复制代码

八、Promise.race()

Promise.race()实现就比较简单了:

static race(promiseArr) {
  return new Promise((resolve, reject) => {
    promiseArr.forEach(p => {
      Promise.resolve(p).then(
        val => resolve(val),
        err => reject(err),
      )
    })
  })
}
复制代码

九、完整代码

// 模拟实现Promise
// Promise利用三大手段解决回调地狱:
// 1. 回调函数延迟绑定
// 2. 返回值穿透
// 3. 错误冒泡

// 定义三种状态
const PENDING = 'PENDING';      // 进行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失败

class Promise {
  constructor(exector) {
    // 初始化状态
    this.status = PENDING;
    // 将成功、失败结果放在this上,便于then、catch访问
    this.value = undefined;
    this.reason = undefined;
    // 成功态回调函数队列
    this.onFulfilledCallbacks = [];
    // 失败态回调函数队列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 成功态函数依次执行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有进行中状态才能更改状态
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 失败态函数依次执行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 立即执行executor
      // 把内部的resolve和reject传入executor,用户可调用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor执行出错,将错误内容reject抛出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function'? onRejected :
      reason => { throw new Error(reason instanceof Error ? reason.message : reason) }
    // 保存this
    const self = this;
    return new Promise((resolve, reject) => {
      if (self.status === PENDING) {
        self.onFulfilledCallbacks.push(() => {
          // try捕获错误
          try {
            // 模拟微任务
            setTimeout(() => {
              const result = onFulfilled(self.value);
              // 分两种情况:
              // 1. 回调函数返回值是Promise,执行then操作
              // 2. 如果不是Promise,调用新Promise的resolve函数
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {
            reject(e);
          }
        });
        self.onRejectedCallbacks.push(() => {
          // 以下同理
          try {
            setTimeout(() => {
              const result = onRejected(self.reason);
              // 不同点:此时是reject
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {
            reject(e);
          }
        })
      } else if (self.status === FULFILLED) {
        try {
          setTimeout(() => {
            const result = onFulfilled(self.value);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          });
        } catch(e) {
          reject(e);
        }
      } else if (self.status === REJECTED) {
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      }
    });
  }
  catch(onRejected) {
    return this.then(null, onRejected);
  }
  static resolve(value) {
    if (value instanceof Promise) {
      // 如果是Promise实例,直接返回
      return value;
    } else {
      // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
      return new Promise((resolve, reject) => resolve(value));
    }
  }
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    })
  }
  static all(promiseArr) {
    const len = promiseArr.length;
    const values = new Array(len);
    // 记录已经成功执行的promise个数
    let count = 0;
    return new Promise((resolve, reject) => {
      for (let i = 0; i < len; i++) {
        // Promise.resolve()处理,确保每一个都是promise实例
        Promise.resolve(promiseArr[i]).then(
          val => {
            values[i] = val;
            count++;
            // 如果全部执行完,返回promise的状态就可以改变了
            if (count === len) resolve(values);
          },
          err => reject(err),
        );
      }
    })
  }
  static race(promiseArr) {
    return new Promise((resolve, reject) => {
      promiseArr.forEach(p => {
        Promise.resolve(p).then(
          val => resolve(val),
          err => reject(err),
        )
      })
    })
  }
}
复制代码

详细解答Promise源码的相似文章

凛冬将至什么水平,2022凛冬之时三年经验前端面经分析理解vue中的diff算法,Vue原理解析(八):一起搞明白令人头疼的diff算法分析Vue 的生命周期之间到底做了什么事清?(源码详解,带你从头梳理组件化流程)分析金三银四,我先面为敬了(腾讯、美团、商汤科技等七家大厂面试有感)分析浏览器的渲染原理及优化方式,浏览器层合成与页面渲染优化分析 连八股文都不懂还指望在前端混下去么分析【7k长文,一次到位】前端八股文再来一遍🧐(图解 + 总结)分析2021年前端各大公司都考了那些手写题(附带代码)分析vue中$router与$route的区别,可能比文档还详细--VueRouter完全指北分析【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)分析