手写一个Promise A+规范

314 阅读7分钟

前言

Promise是什么

Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态) ;状态一旦改变,就不会再变。创造promise实例后,它会立即执行。

Promise A+规范

上面是Promise的一个通俗意义上的解释,也是我们在学习和使用时最先接触到的。其实在这背后,有一个开放、健全且通用的 JavaScript Promise 标准— Promise A+ (promisesaplus.com/)规范 来详尽的描述,我们使用的ES6 Promise 对象就是基于此规范实现的,并且扩展了一些方法。

因此,当你在使用Promise时,如果有一些执行结果无法理解,可以从规范中去看经历了什么样的过程。而手写一个Promise可以帮助我们更加深刻的理解Promise并使用

实现

关于规范的具体内容,请看上面官网,或这个翻译文档, 在此不过多赘述和解读,在具体实现过程中会有说明

构造Promise

let promise new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000)
})

Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。

本规范详细列出了 then 方法的执行过程,所有遵循 Promises/A+ 规范实现的 promise 均可以本标准作为参照基础来实施 then 方法。

从上面可以看到,ES6的Promise在使用时通过 new 方法生成了一个Promise对象,传的是一个参数,即用户要执行的函数,函数中传了两个参数,用来在用户操作完成后更新promise状态, resolve 更新promise状态为 fulfilled , reject 更新promise状态为 rejected 。而在规范中,并没有对构造这一部分的说明。因此我们可以参照上面的例子实现构造函数

// 定义状态
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
// 定义判断方法
function isFn(fn) {
  return typeof fn === "function";
}
function isObj(obj) {
  return Object.prototype.toString.call(obj) === "[object Object]";
}

// 自定义Promise类
class MyPromise {
    constructor(executor) {
    // 传入执行函数
    this._state = PENDING; // 状态
    this._value = undefined; // 终值
    this._reason = undefined; // 据因

    executor(this._resolve.bind(this), this._reject.bind(this));
    }
    _resolve(value) {
        // 可以迁移至执行态或拒绝态
        if (this._state === PENDING) {
            this._state = FULFILLED;
            this._value = value;
        }
    }
    _reject(reason) {
        // 可以迁移至执行态或拒绝态
        if (this._state === PENDING) {
            this._state = REJECTED;
            this._reason = reason;
        }
    }
}

定义then方法

let promise new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000)
})
promise.then(
    (value) => {
      console.log(value);
    },
    (e) => {
      console.error(e);
    }
)

一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。promise 的 then 方法接受两个参数: promise.then(onFulfilled, onRejected)

构造promise, 更新了promise的状态后。根据规范,这个时候要调用用户注册的回调函数,而回调函数是通过then方法传入的。因此可以如下实现then方法


class MyPromise {
    constructor(executor) {
        // ...
        // 支持then多次调用,因此用数组存储每次调用的回调
        this._onResolvedCallbacks = [];
        this._onRejectedCallbacks = [];
        // ...
    }
    then(onResolved, onRejected) {
        // 如果当前是pending状态,将回调存入数组
        if (this._state === PENDING) {
            this._onResolvedCallbacks.push(onResolved);
            this._onRejectedCallbacks.push(onRejected);
        }
        // 当promise已经被决议了(非pending态),注册的回调也需要执行
        if (this._state === FULFILLED) {
            setTimeout(() => {
                isFn(onResolved) && onResolved(this._value)
            })
        }
        if (this._state === REJECTED) {
            setTimeout(() => {
                isFn(onRejected) && onRejected(this._reason)
            })
        }
    }
    _resolve(value) {
        if (this._state === PENDING) {
            this._state = FULFILLED;
            this._value = value;
            // onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用
            // 即回调必须在状态改变后的下一轮循环中调用
            setTimeout(() => {
                this._onResolvedCallbacks.forEach((callback) => {
                    // 如果 onFulfilled 不是函数,其必须被忽略
                    isFn(callback) && callback(value)
                })
            })
        }
    }
    _reject(reason) {
        if (this._state === PENDING) {
            this._state = REJECTED;
            this._reason = reason;
            setTimeout(() => {
                this._onRejectedCallbacks.forEach((callback) => {
                    isFn(callback) && callback(reason)
                })
            })
        }
    }
}

上面实现了规范里面then函数的好几条规范,具体可见注释。通过这个基本的实现,即可使用promise一个基本的功能:一旦promise状态改变,会调用then注册的所有的回调函数

链式调用

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000)
}).then((value) => {
    console.log(value);
    return new Promise((resolve, reject) => {
        setTimeout() => {
            resolve(2)
        })
    })
}).then((value) => {
    console.log(value)
})

then 方法必须返回一个 promise 对象 [注3][3]: promise2 = promise1.then(onFulfilled, onRejected);

Promise的链式调用本质是then方法返回了一个promise对象。在上面并未实现,可以通过在then最后返回当前的promise this 简单实现,它可以满足then中不做任何值的修改的链式回调。但它不能满足then中任何修改的链式回调,因此违背了 必须拥有一个不可变的终值必须拥有一个不可变的据因 的规范。因此需要构造一个新的promise对象,这个对象的构造规则可以参考 Then → 返回Promise解决过程 章节

class MyPromise {
    // ...
    then(onResolved, onRejected) {
        // PENDING状态
        if (this._state === PENDING) {
          // 返回promise2
          let promise2 = new MyPromise((resolve, reject) => {
            // 保存成功回调
            this._onResolvedCallbacks.push(() => {
              this._executor(onResolved, this._value, promise2, resolve, reject);
            });
            // 保存拒绝回调
            this._onRejectedCallbacks.push(() => {
              this._executor(onRejected, this._reason, promise2, resolve, reject, REJECTED);
            });
          });
          return promise2;
        }
        // FULFILLED状态
        if (this._state === FULFILLED) {
          let promise2 = new MyPromise((resolve, reject) => {
            setTimeout(() => {
              this._executor(onResolved, this._value, promise2, resolve, reject);
            });
          });
          return promise2;
        }
        // PREJECTED状态
        if (this._state === REJECTED) {
          let promise2 = new MyPromise((resolve, reject) => {
            setTimeout(() => {
              this._executor(onRejected, this._reason, promise2, resolve, reject, REJECTED);
            });
          });
          return promise2;
        }
    }
	
    // 新promise内部的执行函数
    _executor(handler, data, promise2, resolve, reject, type = FULFILLED) {
        // 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
        // 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
        if (!isFn(handler)) {
          type === FULFILLED ? resolve(data) : reject(data);
        } else {
          let value;
          try {
            value = handler(data);
          } catch (e) {
            // 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
            return reject(e);
          }
          // 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
          this._resolution(promise2, value, resolve, reject);
        }
    }

    // Promise 解决过程
    _resolution(promise, x, resolve, reject) {
        if (x === promise) {
          // x 与 promise相等
          reject(new TypeError("Chaining cycle detected for myPromise #<MyPromise>"));
        } else if (x instanceof MyPromise) {
          // x 为 Promise

          // 如果 x 为 Promise ,则使 promise 接受 x 的状态 注4:
          // 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
          // 如果 x 处于执行态,用相同的值执行 promise
          // 如果 x 处于拒绝态,用相同的据因拒绝 promise
          x.then((y) => {
            this._resolution(promise, y, resolve, reject);
          }, reject);
        } else if (isObj(x) || isFn(x)) {
          // x 为对象或函数
          let then;
          try {
            then = x.then;
          } catch (e) {
            return reject(e);
          }
          if (isFn(then)) {
            // 如果 then 是函数,将 x 作为函数的作用域 this 调用之

            // 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
            let called = false;
            try {
              then.call(
                x,
                // 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
                (y) => {
                  if (!called) {
                    called = true;
                    this._resolution(promise, y, resolve, reject);
                  }
                },
                // 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                (r) => {
                  if (!called) {
                    called = true;
                    reject(r);
                  }
                }
              );
            } catch (e) {
              if (!called) {
                return reject(e);
              }
            }
          } else {
            resolve(x);
          }
        } else {
          resolve(x);
        }
    }
    // ...
}

在返回promise里,根据要求我们构建了一个新的promise对象。在新的promise的执行函数里面,根据当前promise状态保存或执行了一个 _executor 方法,该方法即 Then->返回 里的规范实现。主要处理了非 Promise解决过程 case下的promise状态和值传递。而 _resolution 方法是 Promise解决过程 里规范实现,处理了该case下的promise状态和值传递

异常传递

通过规范,可以看到,promise决议后出现的异常,如回调 onFulFilled onRejected 当中出现的异常, Promise解决过程 出现的异常,都会被返回的 promise reject 。这是因为 promise决议后就不能改变状态等了,而新的返回的promise当中可以保存下来被捕获等。这样形成了一个异常的链式传递。

以上,就是Promise A+ 规范的一个实现过程

测试

完成Promise A+ 规范后,可以使用Github的一个包promises-tests来测试是否符合。

也可以参考我的实现版本 看下效果

ES6版

ES6版本的版本除了对规范的实现外,还补充和扩展的了一些功能。感兴趣的可以去实现这些功能,加深理解和使用

  1. Promise.resolve, Promise.reject, Promise.all, Promise.race Promise.prototype.catch等方法

  2. 当异常到最外层时,除了会传递给返回的promise,还会控制台打印异常,可以通过在 reject中判断是否打印

    _reject(reason) {
        if (this._onRejectedCallbacks.length === 0) {
            console.error(this._reason);
        }
    }
    
  3. 当promise resolve一个Promise或thenable对象时,会在回调中异步的展开,而不是立即执行回调函数。可以在 resolve 中加入此逻辑

    _resolve(value) {
      // 当resolve promise时,传递展开
      if (value instanceof MyPromise) {
        value.then(this._resolve.bind(this), this._reject.bind(this));
        return;
      }
    }
    

Reference

【翻译】Promises/A+规范

手写一个Promise/A+,完美通过官方872个测试用例