符合 Promises/A+ 规范的 Promise 实现

199 阅读8分钟

什么是 Promise

JavaScript的世界中,所有代码都是单线程执行的。异步执行可以用回调函数实现,但是回调函数的多次嵌套会造成回调地狱。Promise 是异步编程的一种解决方案。据MDN Web Docs社区解释,Promise对象用于表示一个异步操作的最终完成 (或失败)及其结果值。一个Promise对象代表一个在这个 Promise被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个Promise,以便在未来某个时候把值交给使用者。

什么是 Promises

Promises 是一种编程模式。Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 Promise 的成功结果或者该 Promise 无法执行的原因。核心 Promises/A+ 规范详细列出了 then 方法的执行过程。Promises/A+ 提供了 Promise 开发的规范,为所有遵循该规范的实现提供了可互操作性的基础。只要参照该规范实现的 Promise 是可以相互兼容的。因此,如果要实现Promise,那么就需要阅读该规范,链接如下:

Promises/A+ 规范

Promises 规范内容介绍

1. Promise 的专业术语

1.1 Promise : 是一个拥有符合本规范的then方法的对象或者函数

1.2 thenable:是一个定义了then方法的对象或函数

1.3 value: 是一个合法的Javascript值(包括undefinedthenable或者promise

1.4 exception:是一个使用throw语句抛出的值

1.5 reason:描述了promise被拒绝的原因

2. Promise 的必备条件

2.1 Promise 的状态

一个promise的状态必须为以下三个值中的一个:pending(等待),fulfilled(已完成),rejected(被拒绝)

2.1.1 当状态为pending时, promise必须具备:

  • 2.1.1.1 可以转变为fulfilled或者rejected状态

2.1.2 当状态为fulfilled时, promise必须具备:

  • 2.1.2.1 不能转变为任何别的状态

  • 2.1.2.2 必须拥有一个 不可改变value

2.1.3 当状态为rejected时, promise必须具备:

  • 2.1.3.1 不能转变为任何别的状态

  • 2.1.3.2 必须拥有一个 不可改变reason

这里的 不可改变 是指,老值和新值可以用===来判断相等,也就是当新值非基本值当时候,只要求其引用地址相等即可,更深层次的属性值可被修改

2.2 then 方法

promise 必须提供一个then方法,来访问当前或最终value或者reason

promisethen方法接受两个参数:

promise.then(onFulfilled, onRejected);

2.2.1 onFulfilledonRejected都是可选参数

  • 2.2.1.1 如果onFulfilled不是一个函数,那么就必须忽略它
  • 2.2.1.2 如果onRejected不是一个函数,那么就必须忽略它

2.2.2 如果onFulfilled是一个函数

  • 2.2.2.1 必须在promise状态为fulfilled时候才能被调用,将promise返回的value作为它的第一个参数
  • 2.2.2.2 不可以在promise状态为fulfilled之前被调用
  • 2.2.2.3 不可以调用超过一次

2.2.3 如果onRejected是一个函数

  • 2.2.2.1 必须在promise状态为rejected时候才能被调用,将promise返回的reason作为它的第一个参数
  • 2.2.2.2 不可以在promise状态为rejected之前被调用
  • 2.2.2.3 不可以调用超过一次

2.2.4 只有等到执行上下文栈只包含平台代码时候,才能调用 onFulfilledonRejected。这句话的意思是 onFulfilledonRejected这两个函数必须在then函数被调用的事件循环里异步执行。可以使用宏任务setTimeoutsetImmediate,或者微任务MutationObserver 或者 process.nextTick 实现。

2.2.5 onFulfilledonRejected只能被当作函数调用(即没有this值)。

2.2.6 同一个promisethen方法可能会被调用多次。

  • 2.2.6.1 当promise的状态是fulfilled时候,所有onFulfilled的回调函数需要按注册顺序依次执行
  • 2.2.6.2 当promise的状态是rejected时候,所有onRejected的回调函数需要按注册顺序依次执行

2.2.7 then必须返回一个promise,【有些实现是存在promise1 === promise2的情况的,那么需要说明】

promise2 = promise1.then(onFulfilled, onRejected);
  • 2.2.7.1 无论是onFulfilled或是onRejected返回了一个值为x,那么都要运行Promise解决过程 [[Resolve]](promise2, x)
  • 2.2.7.2 无论是onFulfilled或是onRejected抛出一个异常e,那么promise2必须拒绝执行,并返回reasone
  • 2.2.7.3 如果onFulfilled不是一个函数,并且promise1状态为fulfilled,那么promise2必须也变成相同的fulfilled状态,并且返回和promise1相同的value
  • 2.2.7.4 如果onRejected不是一个函数,并且promise1状态为rejected,那么promise2必须也变成相同的rejected状态,并且返回和promise1相同的reason

2.3 Promise 解决过程

Prmise 解决过程是一个抽象的操作。它把一个promise和一个值当作输入,用该式子表示 [[Resolve]](promise2, x)

这个方法需要执行以下的步骤:

2.3.1 如果promise和指向相同的对象,那么需要用TypeError为原因拒绝执行promise

2.3.2 如果x是一个promise,则采用它的状态:

  • 2.3.2.1 如果x的状态为pendingpromise必须保持pending直到x状态变为fulfilled或者rejected
  • 2.3.2.2 当x的状态为fulfilled,则让promise用相同的值成功执行
  • 2.3.2.3 当x的状态为rejected,则让promise用相同的原因拒绝执行

2.3.3 否则,如果x是个对象或者函数:

  • 2.3.3.1 将x.then的值赋给变量then
  • 2.3.3.2 如果在获取属性值 x.then的过程中抛出异常e,那么用e为原因拒绝执行promise
  • 2.3.3.3 如果then是一个函数,将x当作this来执行它,将resolvePromise当作第一个参数,rejectPromise当作第二个参数,此时
    • 2.3.3.3.1 当resolvePromise传入值y,执行 [[Resolve]](promise, y)
    • 2.3.3.3.2 当rejectPromise传入原因r,则用r为原因拒绝执行promise
    • 2.3.3.3.3 如果resolvePromiserejectPromise同时被调用,或者各自被调用多次,那么只有第一次被调用的那个生效,其他调用则会被忽略。
    • 2.3.3.3.4 如果调用then抛出异常e
      • 2.3.3.3.4.1 如果调用了resolvePromiserejectPromise,则忽略异常
      • 2.3.3.3.4.2 否则,用e为原因拒绝执行promise
  • 2.3.3.4 如果then不是一个函数,成功执行promise返回x

2.3.4 如果x不是一个对象/函数,成功执行promise返回x

Promise 实现

promise.js 这里我们使用类来实现 Promise

2.1 Promise 的状态

// 首先声明promise的三种状态
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

class Promise {
  constructor(executor) {
    // 初始化
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;

    // 回调函数数组
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    // 2.1 Promise 的状态
    const resolved = (value) => {
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        // 2.2.6.1 当`promise`的状态是`fulfilled`时候,
        // 所有`onFulfilled`的回调函数需要按注册顺序依次执行
        this.onFulfilledCallbacks.forEach((fn) => fn());
      }
    };

    const rejected = (reason) => {
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.reason = reason;
        // 2.2.6.2 当`promise`的状态是`rejected`时候,
        // 所有`onRejected`的回调函数需要按注册顺序依次执行
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      executor(resolved, rejected);
    } catch (e) {
      rejected(e);
    }
  }

  // 2.2 then 方法
  then(onFulfilled, onRejected) {
    // 见 2.2
  }
}

module.exports = Promise;

2.2 then 方法

class Promise {
  constructor(executor) {
    // 见 2.1
  }

  // 2.2 then 方法
  then(onFulfilled, onRejected) {
    // 2.2.1 onFulfilled 和 onRejected 都是可选参数
    onFulfilled = isType(onFulfilled, "Function") ? onFulfilled : (v) => v;
    onRejected = isType(onRejected, "Function")
      ? onRejected
      : (r) => {
          throw r;
        };

    // 2.2.4 只有等到执行上下文栈只包含平台代码时候,才能调用 `onFulfilled`和`onRejected`。
    // 所以执行这两个函数时候,我们在外层包裹了setTimeout
    const promise2 = new Promise((resolve, reject) => {
      switch (this.state) {
        case PENDING:
          // 2.2.6 同一个`promise`的`then`方法可能会被调用多次。
          // 因此会有多个回调函数被注册,则存入回调数组中
          this.onFulfilledCallbacks.push(() => {
            setTimeout(() => {
              try {
                let x = onFulfilled(this.value);
                resolvePromise(promise2, x, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
          });
          this.onRejectedCallbacks.push(() => {
            setTimeout(() => {
              try {
                let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
          });
          break;
        case FULFILLED:
          // 2.2.2 如果`onFulfilled`是一个函数
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
          break;
        case REJECTED:
          // 2.2.3 如果`onRejected`是一个函数
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          });
          break;
      }
    });

    // 2.2.7 then必须返回一个promise
    return promise2;
  }
}

2.3 Promise 解决过程

// 2.3 Promise解决过程
function resolvePromise(promise2, x, resolve, reject) {
  // 2.3.1 如果promise和指向相同的对象,那么需要用TypeError为原因拒绝执行promise
  if (promise2 === x) {
    return reject(new TypeError(`Chaining cycle detected for promise`));
  }

  // 2.3.3 否则,如果`x`是个对象或者函数:
  if (isType(x, "Object") || isType(x, "Function")) {
    //  2.3.3.3.3 如果resolvePromise和rejectPromise同时被调用,
    // 或者各自被调用多次,那么只有第一次被调用的那个生效,其他调用则会被忽略。
    // 因此声明一个标志位
    let called = false;
    try {
      // 2.3.3.1 将x.then的值赋给变量then
      let then = x.then;
      if (isType(then, "Function")) {
        // 如果then是函数,则说明这是个promise
        // 2.3.2 如果`x`是一个`promise`,则采用它的状态:
        // 2.3.3.3 如果then是一个函数,将x当作this来执行它,
        // 将resolvePromise当作第一个参数,rejectPromise当作第二个参数
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        // 2.3.3.4 如果then不是一个函数,成功执行promise返回x值
        resolve(x);
      }
    } catch (e) {
      // 2.3.3.2 如果在获取属性值 x.then的过程中抛出异常e,那么用e为原因拒绝执行promise
      // 2.3.3.3.4 如果调用then抛出异常e
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 2.3.4 如果x不是一个对象/函数,成功执行promise返回x值
    resolve(x);
  }
}

function isType(value, type) {
  return Object.prototype.toString.call(value) === `[object ${type}]`;
}

Promises 测试

当实现完自己的promises后,需要测试一下是否符合aplus规范,则需要使用promises-aplus-tests,步骤如下:

因为该库在测试时候,会调用deferred方法获取到promise实例上的resolvereject方法。在promise.js里加上如下代码,

Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};

接着全局安装该库

npm i promises-aplus-tests -g

然后运行即可

promises-aplus-tests promise.js

之后该程序就会按照 Promises/A+规范来逐条运行测试用例,如果最终通过验证,则会显示 "passing"