来,实现一个 A+ 规范的 Promise 吧

780 阅读10分钟

前言

在前端开发中,Promise 是一个特别重要的概念,很多的异步操作都依赖于 Promise。既然在日常中和它打过那么多的交道,那么我们来自己实现一个 Promise,加深对 Promise 的理解,增强自己的 JavaScript 功力。

本次在实现 Promise 的同时会使用 jest 编写测试用例,以保证实现过程的正确性。

如果想看测试框架的搭建或者完整实现的,可以点击我的github 仓库进行查看,如果你喜欢,欢迎 star,如果你发现我的错误,欢迎提出来。

A+规范

这是一份开放、健全且通用的 Promise 实现规范。由开发者制定,供开发者参考。

这里是官方规范,照着官方规范去实现,就可以写一个属于自己的、符合标准的 Promise

实现

话不多说,我们来开始根据 A+ 规范实现 Promise

规范的第一节是对一些术语的表达,并无实际功能无需实现。

1. 声明 Promise 类

Promise 是一个类(JavsScript 的类是用函数实现的,只是一个语法糖),它必须接受一个函数,否则报错;它还有一个 then 方法。

class PROMISE {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }
  }
  then() {}
}

测试用例如下:

import Promise from '../';

describe('Promise', () => {
  test('是一个类', () => {
    expect(Promise).toBeInstanceOf(Function);
    expect(Promise.prototype).toBeInstanceOf(Object);
  });
  test('new Promise() 必须接受一个函数', () => {
    expect(() => {
      // @ts-ignore
      new Promise();
    }).toThrow(TypeError);
    expect(() => {
      // @ts-ignore
      new Promise('promise');
    }).toThrow(TypeError);
    expect(() => {
      // @ts-ignore
      new Promise(null);
    }).toThrow(TypeError);
  });
  test('new Promise(fn) 会生成一个对象,对象有 then 方法', () => {
    const promise = new Promise(() => {});
    expect(promise.then).toBeInstanceOf(Function);
  });
})

测试用例我后面不再列出来了,有兴趣的可以去我的 github 仓库查看。

2.状态维护

Promise 有三种状态:请求态(pending)、完成态(fulfilled)和拒绝态(rejected)。Promise 一开始是请求态,它可以转为另外两种状态(只允许改变一次),它会在状态改变的时候得到一个值。

class PROMISE {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }
    // 初始状态
    this.status = 'pending';
    // 初始值
    this.value = null;
    // class 内部默认是严格模式,所以需要绑定 this
    executor(this.resolve.bind(this), this.reject.bind(this));
  }
  then() {}
  resolve(value) {
    // 状态保护
    if (this.status !== 'pending') {
      return;
    }
    // 改变状态, 赋值
    this.status = 'fulfilled';
    this.value = value;
  }
  reject(reason) {
    // 状态保护
    if (this.status !== 'pending') {
      return;
    }
    // 改变状态, 赋值
    this.status = 'rejected';
    this.value = reason;
  }
}

到这里,我们就差不多实现了规范 2.1。

3.then

then 可以接受两个函数,会在状态改变之后异步执行,根据规范 2.2.1,如果它们不是函数,就忽略。then 的异步本是一个微任务,这里用宏任务 setTimeout 就将代替一下(如果你想了解微任务、宏任务的知识,欢迎点这里查看我写的关于 Event Loop 的文章)。

then(onFulfilled, onRejected) {
  // 暂时将它们变成空函数,后面再做修改
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => {};
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => {};
  }
  if (this.status === 'fulfilled') {
    // 异步执行,将就用 setTimeout 实现
    setTimeout(() => {
      onFulfilled(this.value);
    });
  }
  if (this.status === 'rejected') {
    setTimeout(() => {
      onRejected(this.value);
    });
  }
}

这里的情况是期待执行 then 函数时,Promise 的状态已经得到了改变。

如果 Promise 的执行函数是一个异步函数,执行 then 的时候,Promise 的状态还没得到改变,那么就需要把 then 接受的两个函数保存起来,等到 resolvereject 的时候执行,这里也要异步执行。

then(onFulfilled, onRejected) {
  // 暂时将它们变成同步的空函数,后面再做修改
  if (typeof onFulfilled !== 'function') {
    onFulfilled = () => {};
  }
  if (typeof onRejected !== 'function') {
    onRejected = () => {};
  }
  // 如果执行 then 的时候,Promise 状态还未发生变化,就先将这两个函数存起来
  if (this.status === 'pending') {
    this.callbacks.push({
      onFulfilled: () => {
        setTimeout(() => {
          onFulfilled();
        });
      },
      onRejected: () => {
        setTimeout(() => {
          onRejected();
        });
      }
    });
  }
  if (this.status === 'fulfilled') {
    // 异步执行,将就用 setTimeout 实现
    setTimeout(() => {
      // 2.2.5
      onFulfilled.call(undefined, this.value);
    });
  }
  if (this.status === 'rejected') {
    setTimeout(() => {
      // 2.2.5
      onRejected.call(undefined, this.value);
    });
  }
}
resolve(value) {
  // 状态保护
  if (this.status !== 'pending') {
    return;
  }
  // 改变状态, 赋值
  this.status = 'fulfilled';
  this.value = value;
  // 如果回调函数数组中有值,说明之前执行过 then,需要调用 then 接受的函数
  this.callbacks.forEach((callback) => {
    // 2.2.5
    callback.onFulfilled.call(undefined, value);
  });
}
reject(reason) {
  // 状态保护
  if (this.status !== 'pending') {
    return;
  }
  // 改变状态, 赋值
  this.status = 'rejected';
  this.value = reason;
  // 如果回调函数数组中有值,说明之前执行过 then,需要调用 then 接受的函数
  this.callbacks.forEach((callback) => {
    // 2.2.5
    callback.onRejected.call(undefined, reason);
  });
}

这样一来,规范的 2.2.2 和 2.2.3 就都实现了。

而且由于 then 里面的 onFulfilledonRejected 都是异步执行的,所以它也满足规范 2.2.4,它会在 Promise 的代码执行之后被调用。

根据规范 2.2.5, onFulfilledonRejected 调用时也不存在 this ,所以用 .call 调用,指定 undefinedthis

规范 2.2.6,如果 then 执行之前 Promise 已经改变了状态,那么直接执行多个 then。否则将 then 的函数参数存在 callbacks 数组中,后面依次调用,实现规范 2.2.6。

4. 链式操作

Promise 有一个 then 方法,then 之后还可以 then,那么让 then 返回一个 Promise 即可,根据规范 2.2.7,我们也必须让 then 返回一个 Promise

根据规范 2.2.7.1,无论是 onFulfilledonrejected,它们返回的值都会被当做 then 返回的新的 Promiseresolve 的值成功处理。

根据规范 2.2.7.2,如果 onFulfilledonRejected 抛出了一个错误 e,那么会被当做 then 返回的新的 Promisereject 的值失败处理。

then(onFulfilled, onRejected) {
  // then 返回一个 Promise
  return new PROMISE((resolve, reject) => {
    // 暂时将它们变成空函数,后面再做修改
    if (typeof onFulfilled !== 'function') {
      onFulfilled = () => {};
    }
    if (typeof onRejected !== 'function') {
      onRejected = () => {};
    }
    // 如果执行 then 的时候,Promise 状态还未发生变化,就先将这两个函数存起来
    if (this.status === 'pending') {
      this.callbacks.push({
        // 这里也需要变了
        onFulfilled: () => {
            setTimeout(() => {
              try {
                // 2.2.5
                const result = onFulfilled.call(undefined, this.value);
                // onFulfilled 的返回值当做新的 Promise 的 resolve 的值被调用
                resolve(result);
              } catch (error) {
                // 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
                reject(error);
              }
            });
          },
          onRejected: () => {
            setTimeout(() => {
              try {
                // 2.2.5
                const result = onRejected.call(undefined, this.value);
                // onRejected 的返回值当做新的 Promise 的 resolve 的值被调用
                resolve(result);
              } catch (error) {
                // 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
                reject(error);
              }
            });
          }
      });
    }
    if (this.status === 'fulfilled') {
      // 异步执行,将就用 setTimeout 实现
      setTimeout(() => {
        try {
          // 2.2.5
          const result = onFulfilled.call(undefined, this.value);
          // onFulfilled 的返回值当做新的 Promise 的 resolve 的值被调用
          resolve(result);
        } catch (error) {
          // 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
          reject(error);
        }
      });
    }
    if (this.status === 'rejected') {
      setTimeout(() => {
        try {
          // 2.2.5
          const result = onRejected.call(undefined, this.value);
          // onRejected 的返回值当做新的 Promise 的 resolve 的值被调用
          resolve(result);
        } catch (error) {
          // 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
          reject(error);
        }
      });
    }
  });
}

规范 2.2.7.3 和 2.2.7.4 表示如果 thenonFulfilledonRejected 不是函数,那么新的 Promise 会用上一个 Promise 成功(resolve)或失败(reject)的值继续成功或失败,也就是会继承上一个 Promise 的状态和值。

举一个例子

new Promise((resolve, reject) => {
  /**
   * 执行函数体
   */
})
  .then()
  .then(
    function A() {},
    function B() {}
  );

因为第一个 then 的参数不是函数,那么会发生穿透传递,所以后一个 then 接受的两个参数 function A 和 function B,会根据最前面那个 Promise 的状态和值来进行调用。

也就是上面的代码其实和下面的代码执行结果一样。

new Promise((resolve, reject) => {
  /**
   * 执行函数体
   */
})
  .then(
    function A() {},
    function B() {}
  );

好的,让我们来实现这个规范。

其实很简单,你上一个 Promise 如果是 resolve 时,那么我用 thenPromiseresolve,且值不变,如果是 reject,那么 thenPromisereject,且值不变。

这里稍微一点点绕,希望你把这里仔细看,完全搞明白。

if (typeof onFulfilled !== 'function') {
  onFulfilled = (value) => {
    // 前面的 Promise 是 resolve 时,会调用 onFulfilled
    // 那么 then 的新 Promise 也 resolve
    // 将状态和值传递给 then 的 then
    resolve(value);
  };
}
if (typeof onRejected !== 'function') {
  onRejected = (reason) => {
    // 前面的 Promise 是 reject 时,会调用 onRejected
    // 那么 then 的新 Promise 也 reject
    // 将状态和值传递给 then 的 then
    reject(reason);
  };
}

其实这里可以简化一下,变成下面这种。

if (typeof onFulfilled !== 'function') {
  onFulfilled = resolve;
}
if (typeof onRejected !== 'function') {
  onRejected = reject;
}

这样,Promise 的链式操作就完成了。

5.实现 2.3.1

接下来我们继续看规范 2.3 的部分。

如果 Promiseresolve 或者 reject 调用的值是同一个,那么应该使 Promise 处于拒绝(reject )态,值为 TypeError

代码如下:

constructor(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError(`Promise resolver ${executor} is not a function`);
  }
  // 初始状态
  this.status = 'pending';
  // 初始值
  this.value = null;
  // 初始回调数组
  this.callbacks = [];
  // class 内部默认是严格模式,所以需要绑定 this
  try {
    executor(this.resolve.bind(this), this.reject.bind(this));
  } catch (error) {
    // 接住 resolve 和 reject 抛出的 TypeError,作为 reject 的值调用
    this.reject(error);
  }
}
/**
* 代码
*/
resolve(value) {
  // 状态保护
  if (this.status !== 'pending') {
    return;
  }
  // 如果 promise 和 resolve 调用的值是同一个,那么就抛出错误
  if (value === this) {
    throw new TypeError();
  }
  // 改变状态, 赋值
  this.status = 'fulfilled';
  this.value = value;
  // 如果回调函数数组中有值,说明之前执行过 then,需要调用 then 接受的函数
  this.callbacks.forEach((callback) => {
    callback.onFulfilled.call(undefined, value);
  });
}
reject(reason) {
  // 状态保护
  if (this.status !== 'pending') {
    return;
  }
  // 如果 promise 和 reject 调用的值是同一个,那么就抛出错误
  if (value === this) {
    throw new TypeError();
  }
  // 改变状态, 赋值
  this.status = 'rejected';
  this.value = reason;
  this.callbacks.forEach((callback) => {
    callback.onRejected.call(undefined, reason);
  });
}

6.实现 2.3.3

规范 2.3.2 是 2.3.3 情况的一个子集,我们直接实现 2.3.3 就可以了。

规范 2.3.3 说了那么多,其实就是在 resolvereject 中添加下面几行代码。

if (value instanceof Object) {
  // 2.3.3.1 2.3.3.2
  const then = value.then;
  // 2.3.3.3
  if (typeof then === 'function') {
    return then.call(
      value,
      this.resolve.bind(this),
      this.reject.bind(this)
    );
  }
}

关于规范 2.3.3.2,我理解的是,并不是说 x.then 是一个异常,而是在取值的过程中发生了一个异常,代码表达如下:

// 不是这种
const X = {
  then: new Error()
}
// 是类似这种的情况
const x = {};
Object.defineProperty(x, 'then', {
  get: function() {
    throw new Error('y');
  }
});

new Promise((resolve, reject) => {
  resolve(x)
}).then((value) => {
  console.log('fulfilled', value)
}, (reason) => {
  console.log('rjected', reason)
})

由于取值 x.then 的过程中抛出了一个异常,被 constructor 中的 try catch 捕捉到了,执行 reject,这里就无需做处理了。

规范 2.3.4 不用特殊实现,说的就是正常情况。

7.完整实现

到这里就把 A+ 规范走了一遍,实现的 Promise 如下:

class PROMISE {
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }
    // 初始状态
    this.status = 'pending';
    // 初始值
    this.value = null;
    // 初始回调数组
    this.callbacks = [];
    // class 内部默认是严格模式,所以需要绑定 this
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      // 接住 resolve 和 reject 抛出的 TypeError,作为 reject 的值调用
      this.reject(error);
    }
  }
  then(onFulfilled, onRejected) {
    // then 返回一个 Promise
    return new PROMISE((resolve, reject) => {
      // then 的穿透传递
      if (typeof onFulfilled !== 'function') {
        onFulfilled = resolve;
      }
      if (typeof onRejected !== 'function') {
        onRejected = reject;
      }
      // 如果执行 then 的时候,Promise 状态还未发生变化,就先将这两个函数存起来
      if (this.status === 'pending') {
        this.callbacks.push({
          onFulfilled: () => {
            setTimeout(() => {
              try {
                const result = onFulfilled.call(undefined, this.value);
                // onFulfilled 的返回值当做新的 Promise 的 resolve 的值被调用
                resolve(result);
              } catch (error) {
                // 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
                reject(error);
              }
            });
          },
          onRejected: () => {
            setTimeout(() => {
              try {
                const result = onRejected.call(undefined, this.value);
                // onRejected 的返回值当做新的 Promise 的 resolve 的值被调用
                resolve(result);
              } catch (error) {
                // 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
                reject(error);
              }
            });
          }
        });
      }
      if (this.status === 'fulfilled') {
        // 异步执行,将就用 setTimeout 实现
        setTimeout(() => {
          try {
            const result = onFulfilled.call(undefined, this.value);
            // onFulfilled 的返回值当做新的 Promise 的 resolve 的值被调用
            resolve(result);
          } catch (error) {
            // 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
            reject(error);
          }
        });
      }
      if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            const result = onRejected.call(undefined, this.value);
            // onRejected 的返回值当做新的 Promise 的 resolve 的值被调用
            resolve(result);
          } catch (error) {
            // 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
            reject(error);
          }
        });
      }
    });
  }
  resolve(value) {
    // 状态保护
    if (this.status !== 'pending') {
      return;
    }
    // 如果 promise 和 resolve 调用的值是同一个,那么就抛出错误
    if (value === this) {
      throw new TypeError('Chaining cycle detected for promise');
    }
    if (value instanceof Object) {
      // 2.3.3.1
      const then = value.then;
      // 2.3.3.3
      if (typeof then === 'function') {
        return then.call(
          value,
          this.resolve.bind(this),
          this.reject.bind(this)
        );
      }
    }
    // 改变状态, 赋值
    this.status = 'fulfilled';
    this.value = value;
    // 如果回调函数数组中有值,说明之前执行过 then,需要调用 then 接受的函数
    this.callbacks.forEach((callback) => {
      callback.onFulfilled.call(undefined, value);
    });
  }
  reject(reason) {
    // 状态保护
    if (this.status !== 'pending') {
      return;
    }
    // 如果 promise 和 reject 调用的值是同一个,那么就抛出错误
    if (reason === this) {
      throw new TypeError('Chaining cycle detected for promise');
    }
    if (reason instanceof Object) {
      // 2.3.3.1
      const then = reason.then;
      // 2.3.3.3
      if (typeof then === 'function') {
        return then.call(
          reason,
          this.resolve.bind(this),
          this.reject.bind(this)
        );
      }
    }
    // 改变状态, 赋值
    this.status = 'rejected';
    this.value = reason;
    this.callbacks.forEach((callback) => {
      callback.onRejected.call(undefined, reason);
    });
  }
}

其中重复的代码我在这里就不抽离出来了,这样方便阅读。

8.静态方法

resolverejectallrace这几个静态方法其实不属于 A+ 规范中,我这里也顺带实现一下。

resolvereject 类似,接受一个值,返回一个 Promise。如果接受的值是一个 Promise,那么就继承该 Promise 的状态和值。

static resolve(value) {
  return new PROMISE((resolve, reject) => {
    if (value instanceof PROMISE) {
      value.then(resolve, reject);
    } else {
      resolve(value);
    }
  });
}
static reject(reason) {
  return new PROMISE((resolve, reject) => {
    if (reason instanceof PROMISE) {
      reason.then(resolve, reject);
    } else {
      reject(reason);
    }
  });
}

all 是接受一个 Promise 数组,返回一个 Promise

这里定义一个 results 数组,然后遍历 Promise 数组,每 resolve 一个 Promise ,就像 results 加入一个 resolve 的值,如果results 的长度与 Promise 数组的长度相同,那么说明全部的 Promiseresolve 了,那么 all 返回的 Promiseresolve 这个数组。

另外,只要 Promise 数组中有一个 Promise 转为了 reject,那么 all 返回的 Promisereject掉。

static all(promiseArray) {
  return new PROMISE((resolve, reject) => {
    const results = [];
    promiseArray.forEach((promise) => {
      promise.then((value) => {
        results.push(value);
        if (results.length === promiseArray.length) {
          resolve(results);
        }
      }, reject);
    });
  });
}

race 也是接受一个 Promise 数组,返回一个 Promise

只有 Promise 数组中有一个 Promise resolve 或者 reject 了,那么 race 返回的 Promiseresolve 或者 reject

static race(promiseArray) {
  return new PROMISE((resolve, reject) => {
    promiseArray.forEach((promise) => {
      promise.then(resolve, reject);
    });
  });
}

感想

从头实现一遍 Promise 的 A+ 规范的过程中,对 Promise 的一些细枝细节都梳理了一遍,一些之前根本没有注意到的地方也给暴露出来了,特别是 x 如果是一个有 then 方法的对象,那么 x 会被包装成一个 Promise,这个地方也是之前没有接触到的。

这个过程我用 TypeScript 实现了一遍,具体代码点这里,其中也包括了我写的测试用例,如果你喜欢,欢迎 star。

如果你发现我实现过程有不对的地方,欢迎与我探讨。