手写Promise核心原理(一)

351 阅读3分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

前面写了Promise的入门和相关方法的使用Promise的关键问题,需要的童鞋请移步。这篇文章写下Promise的核心原理:

  1. Promise的构造函数
  2. 初始化数据
  3. 实现resolve方法
  4. 实现reject方法
  5. 执行器抛出异常也需要返回错误信息

一、Promise构造函数

在定义promise时都使用new关键字来,很明显promise是一个构造函数。 因此可以得出一个最简单的壳子:

function Promise1() {}

在使用promise时总会传入一个方法,我们称之为执行器executor。

执行器包含resolve和reject两个参数。

resolve和reject是定义在promise构造函数中的两个方法。

function Promise1(executor){
    function resolve() {}
    function reject() {}
    executor(resolve, reject);
}

二、初始化数据

由于promise的三个状态比较常用,所以我们将三个状态的字符串设置为常量,方便后面使用。

var PENDING = "pending";
var FULFILLED = "fulfilled";
var REJECTED = "rejected";

promise构造函数中准备一些变量:

  • 变量status来存储promise的状态,初始状态为pending;(Promise 的三种状态:pending-等待,fulfilled-成功,reject-失败,其中最开始为 pending 状态,并且一旦成功或者失败,Promise 的状态便不会再改变)
  • 变量data来存储promise 返回的结果;
  • 变量callbacks来存储then设置的两个回调,每个元素的结构:{ onResolved(){}, onRejected(){} }。这里用数组来存储是为了满足一个promise能设置多个成功/失败回调函数。
function Promise1(executor){
    var _this = this;
    _this.status = PENDING;
    _this.data = undefined;
    _this.callbacks = [];
}

三、实现resolve方法

resolve内部会做哪些事情呢?

  1. 由于promise的状态只能由pending变为fulfilled/rejected,所以要先判断状态是否为pending,若不是则返回,若是则继续执行;
  2. 将promise状态改为fulfilled;
  3. 将传入的数据保存起来,以便后面使用;
  4. 判断callbacks中是否有内容,若有,则执行其中的onResolved方法,由于回调函数必须是异步执行,所以使用setTimeout方法来实现异步执行。

为什么需要使用callbacks来存储回调函数?

因为当用户先指定回调函数时,promise的状态并没有改变,还无法执行回调函数,所以要先将回调函数存起来,等状态改变后再调用。

  • 当用户先指定回调函数再改变状态时,callbacks中才会存入指定的回调函数,这个操作在then方法中会实现。
  • 当用户先改变状态再指定回调函数时,callbacks中是不会存储数据的;
function reslove(value) {
    if (_this.status != PENDING) return;
    _this.status = FULFILLED;
    _this.data = value;
    if (_this.callbacks.length > 0) {
        setTimeout(function() {
            _this.callbacks.forEach(function(callbackObj) {
                callbackObj.onResolved(value);
            });
        });
    }
}

四、实现reject方法

reject内部操作与resolve的接近一致,只是状态和回调函数不同。

  1. 由于promise的状态只能由pending变为fulfilled/rejected,所以要先判断状态是否为pending,若不是则返回,若是则继续执行;
  2. 将状态改为rejected;
  3. 将传入的数据保存起来,以便后面使用;
  4. 判断callbacks中是否有内容,若有,则执行其中的onRejected方法,由于回调函数必须是异步执行,所以使用setTimeout方法来实现异步执行。
function reject(reason) {
  if (_this.status != PENDING) return;
  _this.status = REJECTED;
  _this.data = reason;
  if (_this.callbacks.length > 0) {
    setTimeout(function() {
      _this.callbacks.forEach(function(callbackObj) {
        callbackObj.onRejected(reason);
      });
    });
  }
}

五、执行器抛出异常也需要返回错误信息

Promise的关键问题文中提到改变promise状态的三种方式:

  • resolve(value): 如果当前是pending状态则变为fulfilled状态;
  • reject(reason): 如果当前是pending状态则变为rejected状态;
  • 抛出异常:如果当前是pending状态则变为rejected状态。

执行器在执行过程中可能出现错误,所以当抛出异常时,需要捕获异常并将promise状态改为rejected。因此需要在执行器外面包上一层捕获异常的方法,并在出错时调用reject方法。

try {
    executor(reslove, reject);
} catch (error) {
    reject(error);
}