自己手撸一个符合Promise/A+的Promise

1,056 阅读5分钟

简单介绍

Promise是什么,相信不用说了,写过js的人或多或少都接触过。刚开始用Promise的时候,总感觉这种写法非常的怪异,但是当慢慢熟悉的时候,发现一切都是那么和谐。

我自己理解的Promise用来解决异步回调嵌套的问题,它代表了异步操作的一种结果。它是一种状态机,从实现上来说一种回调包装器(自己的理解看法如有异议,欢迎讨论)。

当真正看了下Promise/A+规范和看了别人的实现代码后,发现Promise的原理和实现也不是很难,所以就有了这篇文章,旨在自我学习,可能也能顺手帮到一些人。这是最后实现的Promise完整代码github原文地址。我会频繁更新学习深入现代js框架的文章, 欢迎star。

Promise规范

Promise在ES6出现之前,就已经有了各个版本的"Promise",但是当这些用在一起的时候,如果没有一个统一的实现标准,那将是灾难性的。所以就有了规范,Promise/A+就是Promise目前最新的规范。规范旨在帮助实现者指定要达到的目标,并不是规定如何实现。当写完自己的Promise后,要确定是否符合Promise/A+规范,可以使用promises-tests来进行测试。

原理和实现

先来想想Promise的实现应该是个什么样的过程。Promise把我们异步的回调函数进行了封装隐藏,让我们可以在then函数以链式的写法在里面写回调和错误处理,所以按照正常思路就是我们应该将then里的回调函数进行存储,然后等异步执行完后,我们由Promise自行调用。

首先是Promise的构造函数

function Promise (executor) {
  // 为了方面后面函数调用,防止this被破坏
  var self = this;
  this.status = 'pending';
  this.value = null;
  this.reason = null;
  // resolve的回调队列
  this.resolveCbs = [];
  // reject的回调队列
  this.rejectCbs = [];

  // resolve的时候改变状态,保存好传入的值,并调用相应的回调队列
  function resolve(value) {
    if (self.status === 'pending') {
      // 由于promise需要异步执行,这里使用setTimeout来延迟执行
      setTimeout(function() {
        self.status = 'fulfilled';
        self.value = value;
        self.resolveCbs.forEach(cb => cb(value));
      });
    }
  }

  /// 与resolve相似,不过这里保存的是原因,改变状态为rejected
  function reject(reason) {
    if (self.status = 'pending') {
      setTimeout(function() {
        self.status = 'rejected';
        self.reason = reason;
        self.rejectCbs.forEach(cb => cb(reason));
      });
    }
  }

  executor(resolve, reject);
}

Promise有三种状态,分别是pendingfulfilledrejected。它的状态可以由pending转为fulfilled,由pending转为rejected,状态一旦确定将无法更改。

有了构造器,下面我们来实现Promise中的关键函数,then函数。为了实现链式调用,我们需要返回一个Promise对象,但是我们不能返回自己,因为Promise的状态不可更改,我们需要返回一个新的Promise对象。基本解释都包含在了注释中

Promise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  var promise = null;

  // onResolved是可选的,当其不存在或不是函数时,将其接受到的值一次往后透传
  onResolved = typeof onResolved === 'function' ? onResolved : function (value) { return value; };
  // onRejected是可选的,当其不存在或不是函数时,将其错误继续向后抛
  onRejected = typeof onRejected === 'function' ? onRejected : function (error) { throw error; };

  // 新的promise状态需要根据x的具体情况来确定
  function resolvePromise(promise, x, resolve, reject) {
    // 这一部分属于Promise/A+规范的Resolution Procedure部分

    // 2.3.1: 如果promise对象和x引用的是同一个对象,那么应该用一个TypeError的错误来reject掉promise
    // 如果两个对象是同一个对象,那么会无限循环调用,会出现错误
    if (promise === x) {
      return reject(new TypeError('Chaining cycle detected for promise!'));
    }

    // 2.3.2: 如果x是一个promise,应该用以下这些来决定它的状态
    if (x instanceof Promise) {
      // 2.3.2.1: 如果x是pending状态,那么promise必须是pending状态,直到x是fulfillded或rejected状态
      // 2.3.2.2: 如果x是fulfilled状态,那么promise需要用相同的值来resolve
      // 2.3.2.3: 如果x是rejected状态,那么promise需要用相同的原因来reject
      if (x.status === 'pending') {
        x.then(function(value) {
          // 由于x可能还是一个promise,所以这里递归调用
          resolvePromise(promise, value, resolve, reject);
        }, reject);
      } else {
        x.then(resolve, reject);
      }
      return;
    }

    // 2.3.3: 如果x是一个对象或者函数,这里是出里thenable的情况,thenable是指具有then函数的对象或函数
    // 2.3.4: 如果x既不是对象也不是函数,那么直接使用x来resolve promise
    if ((x !== null && typeof x === 'object') || typeof x === 'function') {
      var isCalled = false;

      try {
        // 2.3.3.1: 将x.then赋值为then
        var then = x.then;
        // 2.3.3.2: 如果检索到x.then的结果抛出了错误,那么直接reject掉
        // 2.3.3.3: 如果then是一个函数,那么用x作为this,第一个参数是resolvePromise,第二个参数是rejectPromise
        if (typeof then === 'function') {
          // 2.3.3.3.1: 如果resolvePromise被使用一个参数值y调用,执行[[Resolve]](promise, y)
          // 2.3.3.3.2: 如果rejectPromise被使用一个原因r调用,使用r来reject promise
          then.call(x, function (y) {
            // 2.3.3.3.3: 如果resolvePromise和rejectPromise同时被调用,或者这两个函数被使用相同的参数多次调用,那么只执行最开始的,其他的全部忽略
            if (isCalled) return;
            isCalled = true;
            return resolvePromise(promise, y, resolve, reject);
          }, function (r) {
            if (isCalled) return;
            isCalled = true;
            return reject(r);
          });
        } else {
          // 2.3.3.4: 如果then不是函数,用x来resolve promise
          resolve(x);
        }
      } catch(err) {
        // 2.3.3.3.4: 如果调用then的时候抛出错误
        // 2.3.3.3.4.1: 如果resolvePromise和rejectPromise已经被调用了,那么直接忽略掉
        // 2.3.3.3.4.2: 否则使用err来reject promise
        if (isCalled) return;
        isCalled = true;
        reject(err);
      }
    } else {
      resolve(x);
    }
  }

  function handlePromise(modifier, resolve, reject) {
    return function (value) {
      setTimeout(function() {
        try {
          var x = modifier(value);
          resolvePromise(promise, x, resolve, reject);
        } catch(err) {
          reject(err)
        }
      });
    }
  }

  if (self.status === 'fulfilled') {
    promise = new Promise(function (resolve, reject) {
      handlePromise(onResolved, resolve, reject)(self.value);
    });
  } else if (self.status === 'rejected') {
    promise = new Promise(function (resolve, reject) {
      handlePromise(onRejected, resolve, reject)(self.reason);
    });
  } else {
    promise = new Promise(function (resolve, reject) {
      self.resolveCbs.push(handlePromise(onResolved, resolve, reject));
      self.rejectCbs.push(handlePromise(onRejected, resolve, reject));
    });
  }

  return promise;
}

到了这里我们的Promise基本已经实现完成。下面让我们来测试我们的Promise是否符合Promise/A+规范。

测试

这里我们使用promises-tests进行测试,用它进行测试要先实现一个适配器。下面我们按照它的要求来实现一个

Promise.deferred = function () {
  var global = {};

  var promise = new Promise(function (onResolve, onReject) {
    global.onResolve = onResolve;
    global.onReject = onReject;
  });

  var resolve = function (value) {
    global.onResolve(value);
  };

  var reject = function (reason) {
    global.onReject(reason);
  }

  return {
    promise,
    resolve,
    reject
  }
}

然后导出我们的Promise

module.exports = Promise;

调用我们的Promise进行测试

var promisesAplusTests = require('promises-aplus-tests');
var adapter = require('./promise');

promisesAplusTests(adapter);

最后不考虑性能问题,我们的Promise全部通过Promise/A+测试。

其他文章