自己动手撸一个Promises/A+,Promise实现源码教程(一)

479 阅读5分钟

说起前端利器之Promise在座各位应该很熟悉了,但是你真的知道各种.then .catch交叉链式调用后的结果么,比如简单的Promise.reject(1).catch(e => e).catch(e => console.log(e))的打印结果是什么?你又可曾又想过其中是怎么实现的。下面我们一步一步来试着写一个吧!(结果是没有打印)

简单实现一点

也许你并不知道完整的规范(如Promises/A+)需要实现哪些东西。但我们可以从我们平时使用的简单功能来。下方代码截取自阮一峰的博客,链接

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

一个Promise实例有自己内部的状态,从pendingfulfilledrejected,并提供两个方法接受值改变状态

// 定义三种状态
var Status = {
  PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected'
};

function MyPromise(fn) {
  var self = this;
  self.status = Status.PENDING;
  self.value = null;
  self.error = null;

  // 改变状态
  function resolve(value) {
    // 一旦状态改变无法变更
    if (self.status === Status.PENDING) {
      self.value = value;
      self.status = Status.FULFILLED;
    }
  }
  function reject(error) {
    if (self.status === Status.PENDING) {
      self.error = error;
      self.status = Status.REJECTED;
    }
  }

  // 执行新建Promise时传入的方法
  fn(resolve, reject);
}

为了实现如下的调用方式,需要在Promise上新加一个then方法

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

让我们把精力放到后面更关键和复杂的场景下,这里就不贴全部代码了,相信在座各位都能够实现。也可自己试着尝试

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    // TODO
    // 将onFulfilled和onRejected存到Promise的实例里,等到resove、reject调用的时候触发
}

.then链式调用

熟练使用Promise的小伙伴都知道它可以链式调用用以解决串行的请求之类的场景。

// getJSON是一个请求JSON数据的方法,返回一个Promise
getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

那么问题来了,.then(xx函数).then中第一个.then返回了个啥,为啥后面还可以再.then。回答这个问题可以很简单,返回了另一个Promise,也可以很复杂。大家都能看出第一个.then里传入了一个函数,这个函数又return了一个值。于是后面那个then得到的参数就是上一个then里函数返回的值。so far so easy,不过前提是这个返回的值不是一个类Promise对象(先就理解为Promise吧)。

如果上面这个函数返回了一个Promise对象,那么第二个then里面可就不是前面的返回值了,这么说大家其实也知道

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL); // 这里return的是另一个Promise
}).then(function (comments) { // 这里的参数并不是前面返回的Promise,而是Promise的value
  console.log("resolved: ", comments);
}, function (err){
  console.log("rejected: ", err);
});

到这里其实也引出了一个关键操作,我们把处理下方onFulfilled的return值的操作抽象为一个过程,就取个名字叫Promise解析过程(Promise Resolution Procedure)吧。这里搬一个有点难看的表达式出来吧[[Resolve]](promise, x)

// 这里promise2对应上面例子中的.then(xx函数)的返回值,
promise2 = promise1.then(onFulfilled, onRejected);

如果onFulfilled或者onRejected返回了一个x,那么就执行[[Resolve]](promise2, x)。那么这个过程具体干了啥呢?规范里定义的场景就很多了,这里选一些主要的来说下吧。

  • 如果x是一个Promise,那么x的状态改变会同步改变promise2的状态。上面的例子中.then(xx函数)返回的函数就是promise2,他的状态的值都取决于xx函数返回的那个Promise。
  • 如果x不是一个Promise,且它没有.then方法(一般场景没有这种使用场景)。那么直接resolve掉promise2,resolve(x)

好了我们先不实现这个过程,我们先来调用它来实现链式调用。前面说了.then(xx函数)返回了一个Promise,我们可以叫这个为桥梁Promise,起了一个连接作用。

// 先定义[[Resolve]](promise, x)函数的签名
function resolvePromise(promise, x, resolve, reject) {}

因为这个过程需要改变这个promise的状态,所以还需要传入resolve和reject方法

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  var self = this;
  var bridgeAdapter; // 只是为了保存resolve方法
  var bridgePromise = new MyPromise(function(resolve, reject) {
    bridgeAdapter = {
      resolve,
      reject
    }
  });

  if (self.status === Status.PENDING) {
    // 这个数组会在状态变更的时候被调用
    self.onFulfilledCallbacks.push(value => {
      const x = onFulfilled(value);
      // 刚定义的过程方法
      resolvePromise(bridgePromise, x, bridgeAdapter.resolve, bridgeAdapter.reject);
    });
    self.onRejectedCallbacks.push(err => {
      const x = onRejected(err);
      resolvePromise(bridgePromise, x, bridgeAdapter.resolve, bridgeAdapter.reject);
    });

    return bridgePromise;
  }
  // TODO,状态早已变更过的情况
  return bridgePromise;
}

这样一样我们就把这个桥梁Promise交给了下一个.then。下面就是如何去改变这个桥梁Promise的状态,也就是[[Resolve]](promise, x)要怎么实现。其实主要过程很简单,去除各种判断后:

function resolvePromise(promise, x, resolve, reject) {
  var then = x ? x.then : null;
  if (
    typeof then === 'function'
  ) {
    // 就是x.then()
    then.call(x, function(value) {
      resolvePromise(promise, value, resolve, reject);
    }, function(error) {
      reject(error);
    })
  }
  else {
    resolve(x);
  }
}

这里面主要是一个递归,对于返回的x这个Promise,我们需要用then获取它的结果,这里又有一个新的逻辑。

  • 如果x这个Promise被resolve的值是y,继续执行[[Resolve]](promise, y),这个promise还是那个桥梁bridge。

这个情况就真的很神奇了,举个例子

const promise = new Promise(function(resolve, reject) {
  resolve(1);
});

promise
  .then(function(value) {
    return new Promise(function(resolve, reject) {
      resolve(
        new Promise(function (resolve, reject) {
          resolve(3);
        })
      )
    }) // 要是这里再.then一下会发生啥?
  })
  .then(function(value) {
    console.log('value: ', value);
  })

试想一下会打印出来什么?答案可以发到评论里

综上所述

我们有一个Promise的构造函数,里面维护自己的状态,和提供改变状态的方法,并且保存.then注册的回调。

function MyPromise(fn) {
  var self = this;
  self.status = Status.PENDING;
  self.value = null;
  self.error = null;
  self.onFulfilledCallbacks = [];
  self.onRejectedCallbacks = [];
  
  function resolve(value) { // 省略 }
  function reject(error) { // 省略 }
  fn(resolve, reject);
}

然后有一个解析Promise的方法,根据传入的值x改变Promise的状态

function resolvePromise(promise, x, resolve, reject) { // 省略 }

然后就是Promise的.then方法,会生成一个新的桥梁Promise,并把它的生杀大权交给onFulfilled的返回值(resolvePromise)

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  var self = this;
  var bridgeAdapter;
  var bridgePromise = new MyPromise(function(resolve, reject) {
    bridgeAdapter = {
      resolve,
      reject
    }
  });

  // 省略
  self.onFulfilledCallbacks.push(value => {
     const x = onFulfilled(value);
     resolvePromise(bridgePromise, x, bridgeAdapter.resolve, bridgeAdapter.reject);
  });

  return bridgePromise;
}

以上就是整个Promise核心的结构和功能模块,虽然做了很多简化处理,不过最终代码也 不超过百行,为了达到Promises/A+标准还需要补充很多场景的处理才行,那么就请听下回分解了。喜欢的话请点赞、收藏、转发一波~

本文首发于掘金,转载请注明出处

参考资料