一次搞定Promise原理(终极版)

127 阅读9分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

上一节我们实现了一个简单版的Promise,通过手写让我们理解了Promise的原理,可以点击查看。但是,它比较简单薄弱。这一节我们来实现一个完整版的Promise,它遵循Promise A+的标准,且能够通过测试。

我们先搭建一个Promise的骨架:

function MyPromise(executor) {}
MyPromise.prototype.then = function() {}
MyPromise.prototype.catch = function() {}

在开始编写程序之前,我们先了解一下Promise的标准(如果觉得标准过长,可以先跳过看后面的代码实现,在回过来看这里的标准):

  1. Promise的三种状态:pending、fulfilled、rejected

    1. 处于pending中可以转fulfilled或rejected

    2. 处于fulfilled中不能迁移其他任何状态,必须拥有不可变的终值

    3. 处于rejected中不能迁移其他任何状态,必须拥有不可变的拒绝原因

  2. Then方法

    1. Promise拥有一个then方法,且接受两个参数onFulfilled, onRejected

    2. then方法的参数是可选的,如果不是参数可以被忽略

    3. onFulfilled如果是函数,当promise执行结束后被调用,其第一个参数是promise的终值,该方法被调用不超过一次

    4. onRejected如果是函数,当promise被拒绝后,其第一个参数是promise的错误原因,该方法被调用不超过一次

    5. then方法可以被多次调用(链式调用)

      1. promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调

      2. promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调

    6. then必须返回一个promise对象

  3. Promise解决过程

    1. 如果promise和x指向同一对象,以TypeError为因拒绝执行

    2. 如果 x 为 Promise ,则使 promise 接受 x 的状态

      1. 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝

      2. 如果 x 处于执行态,用相同的值执行 promise

      3. 如果 x 处于拒绝态,用相同的据因拒绝 promise

    3. 如果 x 为对象或者函数:

      1. x.then 赋值给 then

      2. 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise

      3. 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:

        1. 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)

        2. 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise

        3. 如果 resolvePromiserejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用

        4. 如果调用 then 方法抛出了异常 e

          1. 如果 resolvePromiserejectPromise 已经被调用,则忽略之

          2. 否则以 e 为据因拒绝 promise

        5. 如果 then 不是函数,以 x 为参数执行 promise

      4. 如果 x 不为对象或者函数,以 x 为参数执行 promise

以上的规范冗长又晦涩,现在我们根据标准一步步拆解来实现通用的Promise:

Promise有三种状态:pending、fulfilled、rejected,同时传入executor作为参数。

// promise的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function MyPromise(executor) {
  this.value = null
  this.reason = null
  this.state = PENDING
  const that = this
}

我们先定义了三种状态:pending、fulfilled、rejected。同时,声明了MyPromise构造函数,定义value来存放异步任务成功的结果,定义reason存放异步任务失败的原因,初始化state为pending状态。 接着加上resolve和rejecte两个函数,这两个函数将作为executor的入参:

function MyPromise(executor) {
  // ...
  // 新增代码
  function resolve(value) {
    if (that.state === PENDING) {
      that.value = value;
      that.state = FULFILLED;
    }
  }
  function reject(reason) {
    if (that.state === PENDING) {
      that.reason = reason;
      that.state = REJECTED;
    }
  }
  
  try {
    executor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

上面的代码,我们新增了resolve和reject函数作为executor的入参。resolve和reject函数中,有判断当前state的状态,只有是pending的状态才能迁移到fulfilled或者rejected的状态,保证了状态的不可逆。那这里resolve和reject两个函数的作用又是什么呢?其实是用来接受异步执行的函数结果:resolve用来接受异步执行成功的值,reject用来接受异步执行失败的原因。最后executor用trycatch来包裹,当执行报错的时候,直接调用reject返回报错原因。

现在我们实现一下then方法:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  // 新增以下代码
  if (typeof onFulfilled !== 'function') {
    onFulfilled = value => value
  }
  if (typeof onRejected !== 'function') {
    onRejected = reason => { throw reason }
  }
  if (this.state === FULFILLED) {
     onFulfilled(this.value)
   } else if (this.state === REJECTED) {
     onRejected(this.reason)
   }
}

在then方法中,如果不传onFulfilled或 onRejected,我们给设定一个默认的函数的方式来实现更加通用的onFulfilled和onRejected。接着判断state为fulfilled,调用onFulfilled并把结果传递出去。如果是拒绝状态,则调用onRejected返回报错原因。

接着我们实现then方法的异步调用:

上面的代码中,调用then之后onFulfilled和onRejected是立即执行的,如果有pending状态的任务,那么它也会立即执行,出现不符合预期的行为。所以我们需要给then添加异步任务,将异步任务添加到队列中,当状态为终态(fulfilled或rejected)时,才按顺序执行异步任务。代码如下:

function MyPromise(executor) {
  // ...
  // 新增代码
  this.fulfilledQueue = [];
  this.rejectedQueue = [];

  function resolve(value) {
    if (that.state === PENDING) {
      that.value = value;
      that.state = FULFILLED;
      // 新增代码
      that.fulfilledQueue.forEach((fn) => fn());
    }
  }
  function reject(reason) {
    if (that.state === PENDING) {
      that.reason = reason;
      that.state = REJECTED;
      // 新增代码
      that.rejectedQueue.forEach((fn) => fn());
    }
  }
  // ...
}

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  // ...
  // 新增代码
  const fulfilledMicrotask = () => {
      queueMicrotask(() => {
        try {
          onFulfilled(this.value);
        } catch (error) {
          reject(error);
        }
      });
    };
    const rejectedMicrotask = () => {
      queueMicrotask(() => {
        try {
          onRejected(this.reason);
        } catch (error) {
          reject(error);
        }
      });
    };
    if (this.state === FULFILLED) {
      fulfilledMicrotask();
    } else if (this.state === REJECTED) {
      rejectedMicrotask();
    } else {
      // 新增代码:pending状态时注册异步任务队列
      this.fulfilledQueue.push(fulfilledMicrotask);
      this.rejectedQueue.push(rejectedMicrotask);
    }
}

我们通过维护两个队列用来存放异步函数的,这两个队列的函数只有在状态迁移到终态的时候才能执行,保证了异步函数在状态改变后才被触发。同时,我们使用queueMicrotask来包装异步函数fulfilledMicrotask 和rejectedMicrotask 。

现在已经实现了异步任务了,接下来我们实现链式调用。所谓链式调用,就是then后面可以继续调用then方法,同时后面的then方法可以拿到前面一个then方法返回的值。代码如下:

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  // ...
  // 新增以下代码
  const p2 = new MyPromise((resolve, reject) => {
    const fulfilledMicrotask = () => {
      queueMicrotask(() => {
        // 新增以下代码
        try {
          const x = onFulfilled(this.value);
          resolutionPromise(p2, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      });
    };
    const rejectedMicrotask = () => {
      queueMicrotask(() => {
        // 新增以下代码
        try {
          const x = onRejected(this.reason);
          resolutionPromise(p2, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      });
    };
    if (this.state === FULFILLED) {
      fulfilledMicrotask();
    } else if (this.state === REJECTED) {
      rejectedMicrotask();
    } else {
      this.fulfilledQueue.push(fulfilledMicrotask);
      this.rejectedQueue.push(rejectedMicrotask);
    }
  });
  return p2;
};

我们可以发现,这里我们通过再次new MyPromise返回自身,实现了then方法可以继续调用then 方法。但是,后面的then方法怎么取到前面一个then 方法的值呢?这里就进入到Promise的解决程序,即resolutionPromise方法。那么我们来看一下resolutionPromise方法(这是最后一个了!)

function resolutionPromise(promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new TypeError("循环引用报错"));
  }
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    let isCall = false; // 标识then是否调用,避免重复调用
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (isCall) return;
            isCall = true;
            resolutionPromise(promise, y, resolve, reject);
          },
          (err) => {
            if (isCall) return;
            isCall = true;
            reject(err);
          }
        );
      } else {
        resolve(x);
      }
    } catch (error) {
      if (isCall) return;
      isCall = true;
      reject(error);
    }
  } else {
    resolve(x);
  }
}

我们来看一下上面的代码:

第2行判断promise是否等于x,符合了Promise解决过程的第1条规则。

接着,我们先跳到33行,这里将x作为结果resolve出去,是不是很熟悉,就构造函数里面的resolve,所以这里就是链式调用中能拿到上一个then方法返回的值。

现在,我们到回第5行开始看,在x为对象或者函数时,设置一个isCall标识,目的是防止then被多次调用。然后将x.then赋值给then变量,如果为then为函数,则通过call该this指向为x,在传入两个函数(实际就是我们前面讲的then的onFulfilled和onRejected),以y为参数的函数如果被执行则递归调用resolutionPromise。以err为参数的函数如果被调用则reject返回错误原因。如果then不是函数则直接返回x。

到这里我们基本就完成了Promise的实现。

有人就会有疑问,Promise的解决过程的第2点的情况,好像没有实现?

是的真的没有吗?实际上是已经实现的,我们回到上面的代码第7行,可以看到我们通过trycatch包裹了我们的代码来实现的。这里如果x是Promise的话,那Promise必定是有then方法的(毕竟是我们自己实现的),那这样的话,就走到了then等于函数的判断逻辑中,这样我们就一次实现了两个逻辑(^▽^)。

到这里我们就真正的完整实现了Promise,那怎么验证我们写的没有问题呢?这里可以通过promises-aplus-tests测试工具来测试。在我们的代码中加上以下代码:

MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });
  return result;
};
module.exports = MyPromise;

然后在我们的当前文件的目录下,按顺序执行以下命令:

npm init -y
npm install promises-aplus-tests -D
npm set-script test "promises-aplus-tests MyPromise"
npm run test

那么我们就可以看到以下的输出结果:

promise-ok_oKZZgtZHkZ3RXXFRwZVpG9.png 看到上面的输出结果,恭喜你实现了一个自己的Promise了~~~


5EE8E639_aVeedLKUnBcTnCkpZHBkbi.jpg 附上狗头保命

这里附上完整的代码,以便各位看官查验:

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

function MyPromise(executor) {
  this.value = null; // 记录异步任务执行成功的结果
  this.reason = null; // 记录异步任务失败的结果
  this.state = PENDING; // 异步任务的状态,当前为pending
  const that = this;

  this.fulfilledQueue = [];
  this.rejectedQueue = [];

  function resolve(value) {
    if (that.state === PENDING) {
      that.value = value;
      that.state = FULFILLED;
      that.fulfilledQueue.forEach((fn) => fn());
    }
  }

  function reject(reason) {
    if (that.state === PENDING) {
      that.reason = reason;
      that.state = REJECTED;
      that.rejectedQueue.forEach((fn) => fn());
    }
  }

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

MyPromise.prototype.then = function (onFulfilled, onRejected) {
  if (typeof onFulfilled !== "function") {
    onFulfilled = (value) => value;
  }
  if (typeof onRejected !== "function") {
    onRejected = (reason) => {
      throw reason;
    };
  }
  // 返回一个Promise对象且then多次调用
  const p2 = new MyPromise((resolve, reject) => {
    const fulfilledMicrotask = () => {
      queueMicrotask(() => {
        try {
          const x = onFulfilled(this.value);
          resolutionPromise(p2, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      });
    };
    const rejectedMicrotask = () => {
      queueMicrotask(() => {
        try {
          const x = onRejected(this.reason);
          resolutionPromise(p2, x, resolve, reject);
        } catch (error) {
          reject(error);
        }
      });
    };
    if (this.state === FULFILLED) {
      // 异步执行结果
      fulfilledMicrotask();
    } else if (this.state === REJECTED) {
      rejectedMicrotask();
    } else {
      // pending状态 注册异步任务队列
      this.fulfilledQueue.push(fulfilledMicrotask);
      this.rejectedQueue.push(rejectedMicrotask);
    }
  });
  return p2;
};

function resolutionPromise(promise, x, resolve, reject) {
  if (promise === x) {
    return reject(new TypeError("循环引用报错"));
  }
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    let isCall = false; // 标识then是否调用,避免重复调用
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (isCall) return;
            isCall = true;
            resolutionPromise(promise, y, resolve, reject);
          },
          (err) => {
            if (isCall) return;
            isCall = true;
            reject(err);
          }
        );
      } else {
        resolve(x);
      }
    } catch (error) {
      // 这里漏了
      if (isCall) return;
      isCall = true;
      reject(error);
    }
  } else {
    resolve(x);
  }
}

MyPromise.deferred = function () {
  var result = {};
  result.promise = new MyPromise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });
  return result;
};
module.exports = MyPromise;

参考资料:

Promise A+ 标准: promisesaplus.com/

Promise A+ 中文版: m.ituring.com.cn/article/665…