今天要写一个符合 a+ 规范的Promise。
a+规范地址(保姆级的开发规范):https://promisesaplus.com/
Promise 是基于回调实现的,通过回调函数队列来管理异步代码。这点与发布-订阅模式很相似。

我们一步步来实现。

Promise A+

术语

Promise A+ 规范有几个专门的术语,先来介绍下,以便后面的介绍。

  • promise 是一个对象或者函数,拥有一个then方法,这个方法行为符合 A+ 规范。
  • thenable 是一个对象或者函数,这个对象或函数定义了一个 then 方法。
  • value 是任何合法的 JavaScript 值,包括undefined,thenable 和 promise。
  • exception 异常,是用throw抛出的错误。
  • reason 原因,是一个值表明 promise 为什么被拒绝。

必备项

Promise的状态

promise 有三种状态:pending(初始态/等待态)、fulfilled(成功态/完成态)、rejected(失败态/拒绝态)。promise必定是这三个状态之一。有以下几个特点:

  • 当 promise 是初始态时,状态可以转变为成功态或者失败态。
  • 当 promise 是完成态时,状态不能改变为其他状态,并且必定有一个值 value(返回之后不可改变)。
  • 当 promise 是失败态时,状态不能改变为其他状态,并且必定有一个原因 reason(返回之后不可改变)。

    1. function Promise() {
    2. this.status = 'pending'; // pending / fulfilled / rejected
    3. }

    then 方法

    promise 必定会提供一个 then 方法获取它当前的或者最终的值或原因。then 方法接受两个函数作为参数 onFulfilled 和 onRejected,使用如下:

    1. promise.then(onFulfilled, onRejected);
  • onFulfilled 和 onRejected 都是可选的。

    • 如果 onFulfilled 不是一个函数,就要被忽略。
    • 如果 onRejected 不是一个函数,就要被忽略。
  • 如果 onFulfilled 是一个函数。
    • 它一定是 promise 在成功之后被调用,并且以 promise 的值 value 作为第一个参数。
    • 它不能在成功之前被调用。
    • 它不能被调用多次。
  • 如果 onRejected 是一个函数。
    • 它一定是 promise 在失败之后被调用,并且以 promise 的原因 reason 作为第一个参数。
    • 它不能在失败之前被调用。
    • 它不能被调用多次。
  • onFulfilled 和 onRejected 必须直到执行上下文栈仅包含平台代码才能调用。
  • onFulfilled 和 onRejected 必须作为函数被调用。
  • then 方法可以被调用多次,在同一个promise内。
    • 当 promise 成功之后,所有的各自的 onFulfilled 回调函数都将按照他们起源的then方法的顺序被执行被执行。
    • 当 promise 失败之后,所有的各自的 onRejected 回调函数都将按照他们起源的then方法的顺序被执行被执行。
  • then 方法会返回一个新的 promise。

    1. promise2 = promise1.then(onFulfilled, onRejected);
    • 无论是 onFulfilled 还是 onRejected 返回了一个值 x,都要运行 promise 解决方案。
    • 无论是 onFulfilled 还是 onRejected 抛出了错误异常,promise2 必须被拒绝并且以 e 作为参数。
    • 如果 onFulfilled 不是一个函数,并且 promise1 已经成功,promise2 必须成功并且返回相同的值。
    • 如果 onRejected 不是一个函数,并且 promise1 已经失败,promise2 必须失败并且返回相同的原因。

      The Promise Resolution Procedure

      Promise 的解决程序是一个抽象操作,取得输入的 promise 和值 x(结果)。
  • 如果 promise 和 x 引用的同一个对象,就抛出一个 TypeError。

  • 如果 x 是个 promise,则采用它的状态。
    • 如果 x 是等待态,promise 保持 pending 直到它被解决或者拒绝。
    • 如果 x 被解决,采用相同的值 value。
    • 如果 x 被拒绝,采用相同的原因 reason。
  • 否则 x 是个对象或者方法
    • let then = x.then;
    • 如果取到的属性 x.then 导致抛出异常,拒绝 promise 并把抛出的错误 e 作为原因。
    • 如果 then 是一个方法,将 then 的 this 绑定为 x,第一个参数为解决函数 resolvePromise,第二个参数为拒绝函数 rejectPromise。
    • 当 resolvePromise 被调用时,会被传入值 y,并且递归调用 Promise 解决程序。
    • 当 rejectPromise 被调用时,会被传入原因 r,然后以 r 为参数拒绝 promise2。
      • 如果 resolvePromise 和 rejectPromise 都被调用,或者对同一个参数进行多次调用,则第一次调用优先,并且任何进一步的调用都会被拒绝。
      • 如果 then 抛出异常 e
        • 如果 resolvePromise 和 rejectPromise 已经被调用则忽略它
        • 否则以 e 为原因拒绝 promise2
    • 如果 then 不是一个函数,以 x 为参数解决promise
  • 如果 x 不是一个对象或者函数,以 x 为参数解决promise

实现主干代码

构造函数

首先,我们要明确 Promise 是通过 new 声明的,所以必然是个构造函数,并且接受一个函数(executor)作为参数。executor 函数提供两个内部函数 resolve 和 reject 作为参数:

  • resolve 当成功时调用,接收一个 value 作为成功的值
  • reject 当失败时调用,接受一个 reason 作为失败的原因

    1. // Promise 构造函数
    2. function Promise(executor) {
    3. function resolve(value) { //变成成功态
    4. //...
    5. }
    6. function reject(reason) { //变成失败态
    7. //...
    8. }
    9. executor(resolve, reject);//立即执行
    10. }

    下面添加一些变量用于保存内部数据:

  • state 用于保存 promise的三个状态:pending、fulfilled、rejected。

  • value 保存成功返回的值。
  • reason 保存失败返回的原因。

    1. function Promise(executor) {
    2. this.status = 'pending'; // pending / fulfilled / rejected
    3. this.value = undefined;
    4. this.reason = undefined;
    5. function resolve(value) { //变成成功态
    6. //...
    7. }
    8. function reject(reason) { //变成失败态
    9. //...
    10. }
    11. executor(resolve, reject);//立即执行
    12. }

    有了状态、值、原因之后,完善一下 resolve 和 reject,在前文的 A+ 规范中写明了,只有 pending状态的promsie才能改变状态,在其他状态下无法变更为其他状态,所以代码如下:

    1. //...
    2. let self = this; //由于 resolve 和 reject 是window调用的,所以这边保存下this
    3. function resolve(value) { //变成成功态
    4. if (self.status === 'pending') {
    5. self.value = value;
    6. self.status = 'fulfilled';
    7. }
    8. }
    9. function reject(reason) { //变成失败态
    10. if (self.status === 'pending') {
    11. self.reason = reason;
    12. self.status = 'rejected'
    13. }
    14. }
    15. //...

    到目前为止 resolve 和 reject 还缺少一些功能,即promise完成或者失败后,需要执行 then 方法中的回调函数,promise 是基于回调函数来实现的,其实就是发布订阅,所以需要通过中间数组来保存回调函数,这边onFulfilled和 onRejected 各需要一个,并且需要再改变状态后执行,如下: ```javascript

function Promise(executor) { this.status = ‘pending’; // pending / fulfilled / rejected this.value = undefined; this.reason = undefined;

  1. this.onFulfilledCallbacks = [];
  2. this.onRejectedCallbacks = [];
  3. let self = this;
  4. function resolve(value) { //变成成功态
  5. if (self.status === 'pending') {
  6. self.value = value;
  7. self.status = 'fulfilled';
  8. self.onFulfilledCallbacks.forEach(fn => fn());
  9. }
  10. }
  11. function reject(reason) { //变成失败态
  12. if (self.status === 'pending') {
  13. self.reason = reason;
  14. self.status = 'rejected';
  15. self.onRejectedCallbacks.forEach(fn => fn());
  16. }
  17. }
  18. executor(resolve, reject);//立即执行

}

  1. <a name="WSs0I"></a>
  2. ### then 方法
  3. <a name="dUvPr"></a>
  4. #### 入队
  5. 如何执行回调队列已经实现了,接下来就是通过 then 方法将成功回调和失败回调放入回调队列中。
  6. - 当 promise 状态为 pending 时,将回调函数放入回调队列中。
  7. - 当 promise 状态为 fulfilled 或者 rejected 时,立即执行。
  8. 如下:
  9. ```javascript
  10. Promise.prototype.then = function (onFulfilled, onRejected) {
  11. let self = this;
  12. if (self.status === 'fulfilled') {
  13. onFulfilled(self.value);
  14. }
  15. if (self.status === 'rejected') {
  16. onRejected(self.reason);
  17. }
  18. if (self.status === 'pending') {
  19. self.onFulfilledCallbacks.push(function () {
  20. onFulfilled(self.value)
  21. })
  22. self.onRejectedCallbacks.push(function () {
  23. onRejected(self.reason)
  24. })
  25. }
  26. }

实现链式调用

每一个 promise 的状态在改变之后就无法改变了,所以每个 then 方法必须返回一个新的 promise,保证链式调用。

  1. function resolvePromise(promise2, x, resolve, reject){
  2. //... promise 解决程序
  3. }
  4. Promise.prototype.then = function (onFulfilled, onRejected) {
  5. let self = this;
  6. let promise2 = new Promise(function(resolve, reject){
  7. if (self.status === 'fulfilled') {
  8. let x = onFulfilled(self.value);
  9. resolvePromise(promise2, x, resolve, reject);
  10. }
  11. if (self.status === 'rejected') {
  12. let x = onRejected(self.reason);
  13. resolvePromise(promise2, x, resolve, reject);
  14. }
  15. if (self.status === 'pending') {
  16. self.onFulfilledCallbacks.push(function () {
  17. let x = onFulfilled(self.value);
  18. resolvePromise(promise2, x, resolve, reject);
  19. })
  20. self.onRejectedCallbacks.push(function () {
  21. let x = onRejected(self.reason);
  22. resolvePromise(promise2, x, resolve, reject);
  23. })
  24. }
  25. })
  26. return promise2;
  27. }

这边引用了一个 resolvePromise 方法,主要是对返回值的处理,详情见 A+ 规范。同时这边用 setTimeout 包裹起来,原因是在 promise2 实例化的过程中,调用 promise2 是undefined,使用宏任务可以使 执行完主栈代码之后再调用 resolvePromise 方法。

参数的可选性

  1. Promise.prototype.then = function (onFulfilled, onRejected) {
  2. //参数的可选性
  3. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
  4. onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
  5. //...
  6. }

错误处理

  1. Promise.prototype.then = function (onFulfilled, onRejected) {
  2. //参数的可选性
  3. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
  4. onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
  5. let self = this;
  6. let promise2 = new Promise(function (resolve, reject) {
  7. if (self.status === 'fulfilled') {
  8. setTimeout(() => {
  9. try {
  10. let x = onFulfilled(self.value);
  11. resolvePromise(promise2, x, resolve, reject);
  12. } catch (e) {
  13. reject(e);
  14. }
  15. })
  16. }
  17. if (self.status === 'rejected') {
  18. setTimeout(() => {
  19. try {
  20. let x = onRejected(self.reason);
  21. resolvePromise(promise2, x, resolve, reject);
  22. } catch (e) {
  23. reject(e);
  24. }
  25. })
  26. }
  27. if (self.status === 'pending') {
  28. self.onFulfilledCallbacks.push(function () {
  29. setTimeout(() => {
  30. try {
  31. let x = onFulfilled(self.value);
  32. resolvePromise(promise2, x, resolve, reject);
  33. } catch (e) {
  34. reject(e);
  35. }
  36. })
  37. })
  38. self.onRejectedCallbacks.push(function () {
  39. setTimeout(() => {
  40. try {
  41. let x = onRejected(self.reason);
  42. resolvePromise(promise2, x, resolve, reject);
  43. } catch (e) {
  44. reject(e);
  45. }
  46. })
  47. })
  48. }
  49. })
  50. return promise2;
  51. }

The Promise Resolution Procedure(resolvePromise 函数)

  • 判断 promise2 和 x 是否指向同一对象,是则抛出异常 循环引用。
  • 判断是否是promise,由于要兼容其他人的Promise,所以采用

    1. //满足条件则认为是 promise
    2. if(x !== null && (typeof x === 'object' || typeof x === 'function')){
    3. //...
    4. }
  • 需要判断 resolve和reject是否已经被调用,调用过后忽略后面的调用。

    1. // 这个方法要兼容别人的promise 严谨一些
    2. function resolvePromise(promise2, x, resolve, reject) {
    3. if (promise2 === x) { //防止返回的 promise 和 then 方法返回的promise是同一个
    4. throw new TypeError('循环引用');
    5. }
    6. if (x !== null && (typeof x === 'object' || typeof x === 'function')) { //是 Promise
    7. let called;
    8. try {
    9. let then = x.then; //看看这个对象有没有 then方法,如果有 说明x是promise {then: undefined}
    10. if (typeof then === 'function') {
    11. then.call(x, y => {
    12. if (called) return;
    13. called = true;
    14. //如果返回的是一个promise,resolve的结果可能还是一个promise,递归解析知道这个y是个常量为止
    15. resolvePromise(promise2, y, resolve, reject);
    16. }, r => {
    17. if (called) return; //防止调用成功后又调用失败
    18. called = true;
    19. reject(r);
    20. })
    21. } else {
    22. resolve(x);// {then:{}} {then:123}
    23. }
    24. } catch (e) { //这个then方法是通过 Object.defineProperty 定义的
    25. if (called) return;
    26. called = true; //这个判断为了防止出错后 继续调用成功逻辑
    27. reject(e);
    28. }
    29. } else {
    30. resolve(x); // x 就是普通对象
    31. }
    32. }

    完善一下整体的错误处理

    ```javascript function Promise(executor) { this.status = ‘pending’; // pending / fulfilled / rejected this.value = undefined; this.reason = undefined;

    this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; let self = this;

    function resolve(value) { //变成成功态

    1. if (value instanceof Promise) {
    2. return value.then(resolve, reject)
    3. }
    4. if (self.status === 'pending') {
    5. self.value = value;
    6. self.status = 'fulfilled';
    7. self.onFulfilledCallbacks.forEach(fn => fn());
    8. }

    }

    function reject(reason) { //变成失败态

    1. if (self.status === 'pending') {
    2. self.reason = reason;
    3. self.status = 'rejected';
    4. self.onRejectedCallbacks.forEach(fn => fn());
    5. }

    } try {

    1. executor(resolve, reject);//立即执行

    } catch (e) {

    1. reject(e)

    } }

// 这个方法要兼容别人的promise 严谨一些 function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { //防止返回的 promise 和 then 方法返回的promise是同一个 throw new TypeError(‘循环引用’); }

  1. if (x !== null && (typeof x === 'object' || typeof x === 'function')) { //是 Promise
  2. let called; //有的人 promise 既可以调成功 也可以调失败,所以这边兼容下,防止他重复调用
  3. try {
  4. let then = x.then; //看看这个对象有没有 then方法,如果有 说明x是promise {then: undefined}
  5. if (typeof then === 'function') {
  6. then.call(x, y => {
  7. if (called) return;
  8. called = true;
  9. //如果返回的是一个promise,resolve的结果可能还是一个promise,递归解析知道这个y是个常量为止
  10. resolvePromise(promise2, y, resolve, reject);
  11. }, r => {
  12. if (called) return; //防止调用成功后又调用失败
  13. called = true;
  14. reject(r);
  15. })
  16. } else {
  17. resolve(x);// {then:{}} {then:123}
  18. }
  19. } catch (e) { //这个then方法是通过 Object.defineProperty 定义的
  20. if (called) return;
  21. called = true; //这个判断为了防止出错后 继续调用成功逻辑( 这里取 then 的时候可能会报错,但是报错之后可能继续往下走)
  22. reject(e);
  23. }
  24. } else {
  25. resolve(x); // x 就是普通对象
  26. }

}

Promise.prototype.then = function (onFulfilled, onRejected) { //参数的可选性 onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : val => val; onRejected = typeof onRejected === ‘function’ ? onRejected : err => { throw err };

  1. let self = this;
  2. let promise2 = new Promise(function (resolve, reject) {
  3. if (self.status === 'fulfilled') {
  4. setTimeout(() => {
  5. try {
  6. let x = onFulfilled(self.value);
  7. resolvePromise(promise2, x, resolve, reject);
  8. } catch (e) {
  9. reject(e);
  10. }
  11. })
  12. }
  13. if (self.status === 'rejected') {
  14. setTimeout(() => {
  15. try {
  16. let x = onRejected(self.reason);
  17. resolvePromise(promise2, x, resolve, reject);
  18. } catch (e) {
  19. reject(e);
  20. }
  21. })
  22. }
  23. if (self.status === 'pending') {
  24. self.onFulfilledCallbacks.push(function () {
  25. setTimeout(() => {
  26. try {
  27. let x = onFulfilled(self.value);
  28. resolvePromise(promise2, x, resolve, reject);
  29. } catch (e) {
  30. reject(e);
  31. }
  32. })
  33. })
  34. self.onRejectedCallbacks.push(function () {
  35. setTimeout(() => {
  36. try {
  37. let x = onRejected(self.reason);
  38. resolvePromise(promise2, x, resolve, reject);
  39. } catch (e) {
  40. reject(e);
  41. }
  42. })
  43. })
  44. }
  45. })
  46. return promise2;

}

  1. <a name="JUTfh"></a>
  2. ##
  3. <a name="Dm00q"></a>
  4. ## 延迟对象
  5. <a name="tVOAj"></a>
  6. ### 实现
  7. ```javascript
  8. Promise.deferred = function(){
  9. let dtd = {};
  10. dtd.promise = new Promise((resolve, reject)=>{
  11. dtd.resolve = resolve;
  12. dtd.reject = reject;
  13. })
  14. return dtd;
  15. }

使用

  1. let fs = require('fs');
  2. let Promise = require('./promise');
  3. function read(url) {
  4. // 延迟对象
  5. let defer = Promise.deferred(); //{promise, resolve, reject}
  6. fs.readFile(url, 'utf8', function (err, data) {
  7. if (err) return defer.reject(err);
  8. defer.resolve(data);
  9. })
  10. return defer.promise;
  11. }
  12. read('./promise/name.txt').then(data=>{
  13. console.log(data)
  14. },err=>{
  15. console.log(err)
  16. })

A+ 规范测试程序

到此为止,实现了主干代码,并且配置好 延迟对象后,可以执行单元测试对程序进行验证。配置如下:

  • 安装包 npm install promises-aplus-tests
  • 运行命令 npx promises-aplus-tests 路径

得到结果 872 个测试程序运行通过,即符合A+规范

其他原型方法

Promse.prototype.catch()

  1. Promise.prototype.catch = function(errorCallback){
  2. return this.then(null, errorCallback);
  3. }

Promise.prototype.finally()

  1. Promise.prototype.finally = function (callback) {
  2. return this.then(value => {
  3. return Promise.resolve(callback()).then(()=>{
  4. return value;
  5. })
  6. }, reason => {
  7. return Promise.resolve(callback()).then(()=>{
  8. throw reason;
  9. })
  10. })
  11. }

静态方法

Promise.resolve()

  1. Promise.resolve = function(value){
  2. return new Promise((resolve, reject)=>{
  3. resolve(value);
  4. })
  5. }

Promise.reject()

  1. Promise.reject = function(reason){
  2. return new Promise((resolve, reject)=>{
  3. reject(reason);
  4. })
  5. }

Promise.all()

  1. Promise.all = function (values) {
  2. return new Promise((resolve, reject) => {
  3. let arr = [];
  4. let count = 0;
  5. function processData(key, value) {
  6. arr[key] = value;
  7. if (++count === values.length) {
  8. resolve(arr)
  9. }
  10. }
  11. for (let i = 0; i < values.length; i++) {
  12. let current = values[i];
  13. let then = current.then;
  14. if (then && typeof then === 'function') {
  15. then.call(current, y => {
  16. processData(i, y);
  17. }, reject)
  18. } else {
  19. processData(i, values[i])
  20. }
  21. }
  22. })
  23. }

Promise.race()

  1. Promise.race = function (values) {
  2. return new Promise(function(resolve, reject) {
  3. for (let i = 0; i < values.length; i++) {
  4. let current = values[i];
  5. let then = current.then;
  6. if (then && typeof then === 'function') {
  7. then.call(current, y => {
  8. resolve(y)
  9. }, reject)
  10. } else {
  11. resolve(current);
  12. break;
  13. }
  14. }
  15. })
  16. }

Node 方法 Promise 化

  1. //node中的所有方法 都是错误优先 第二个就是结果 bluebird
  2. function promisify(fn) {// 把方法promise话
  3. return function () {
  4. return new Promise((resolve, reject) => {
  5. //fn.call(null, ...args)
  6. fn(...arguments, function (err, data) {
  7. if (err) reject(err);
  8. resolve(data);
  9. })
  10. })
  11. }
  12. }
  13. function promisifyAll(obj) {
  14. for (let key in obj) {//遍历整个对象 如果是函数的 我就把方法重写
  15. if(typeof obj[key] === 'function'){
  16. obj[key + 'Async'] = promisify(obj[key]); //把每个方法都promise化
  17. }
  18. }
  19. }