Promise源码解析

Promise源码解析

纸上得来终觉浅,绝知此事要躬行。之前只是很浅显的知道Promise的用法,也大概猜测到它的内部是如何实现的。但是总是有一种不深究一下就不踏实的感觉。于是从npm上获得早期的Promise源代码,拿过来读一读,做一做笔记。

Promise的源码写的非常的巧妙,凭空阅读会陷入入其中无法自拔。

简单的Promise用法

一个简单的Promise用法如下:

const promiseObj = new Promise(function (resolve, reject) {
  // 代码执行
  resolve('value');

  //or
  reject('error');
});
promiseObj.then(function (value) {
  // do sth...
}, function (error) {
  // deal excption...
});
promiseObj.catch(function (error) { 
  // catch excption...
});

我们就从这个简单的例子开始分析Promise的执行是怎么样的。

Promise的源代码位于:https://github.com/stefanpenner/es6-promise.git ,这里采用的版本为0.1.0版本。

我们从Promise的构造函数开始说起:

function Promise(resolver) {

  ... // 省略的部分为必要的校验

  this._subscribers = [];

  invokeResolver(resolver, this);
}

_subscribers对象用于存储这个Promise实例所对应的观察者。紧接着执行invokeResolver();

function invokeResolver(resolver, promise) {
  function resolvePromise(value) {
    resolve(promise, value); // p2, p1
  }

  function rejectPromise(reason) {
    reject(promise, reason);
  }

  try {
    resolver(resolvePromise, rejectPromise);
  } catch(e) {
    rejectPromise(e);
  }
}

invokeResolver为关键的一环。在这里,构造Promise对象时所传入的方法会被执行,并将执行方法所需的两个回调参数resolvePromise和rejectPromise传了进去,方便业务代码通知Promise对象执行结果。

如果我们的代码很简单的执行了一行代码,例如:

  resolve('Hello');

那么resolvePromise会紧接着调用resolve方法。我们进入resolve方法一探究竟:

function resolve(promise, value) { // promise为新构造的Promise对象,value = 'Hello'.
  if (promise === value) {
    fulfill(promise, value);
  } else if (!handleThenable(promise, value)) {
    fulfill(promise, value);
  }
}

我们现在的逻辑会直接进入fulfill方法:

function fulfill(promise, value) {// promise为新构造的Promise对象,value = 'Hello'.
  if (promise._state !== PENDING) { return; } // 当前不满足,默认为PENDING状态
  promise._state = SEALED;// promise._state = SEALED 
  promise._detail = value;// promise._detail = 'Hello' 

  config.async(publishFulfillment, promise);
}

到这里,将结果值赋值给了Promise对象。也就是说Promise对象保留了计算后的结果值’Hello’。然后我们看一下config.async()是个什么鬼:

function asap(callback, arg) {
  var length = queue.push([callback, arg]);
  if (length === 1) {
    // If length is 1, that means that we need to schedule an async flush.
    // If additional callbacks are queued before the queue is flushed, they
    // will be processed by this flush that we are scheduling.
    scheduleFlush();
  }
}

上面这段代码位于lib/promise/asap.js中。它主要用来将任务callback加入下一个时间片中。执行到这里会将[publishFulfillment, promise]组成一个数组放在队列中,然后紧接着根据平台进行任务刷新,也就是执行scheduleFlush();

不过,不管scheduleFlush方法再怎么快,它也是被放在了所有事件最后才执行。所以接下来执行的代码是主线程继续执行的代码:

promiseObj.then(function (value) {
  // do sth...
}, function (error) {
  // deal excption...
});
promiseObj.catch(function (error) { 
  // catch excption...
});

到这里我们需要看看Promise.then方法是怎么执行的:

  then: function(onFulfillment, onRejection) {
    var promise = this;

    var thenPromise = new this.constructor(function() {});

    if (this._state) {
      var callbacks = arguments;
      config.async(function invokePromiseCallback() {
        invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
      });
    } else {
      subscribe(this, thenPromise, onFulfillment, onRejection);
    }

    return thenPromise;
  },

在调用then方法时,产生了一个新的Promise对象thenPromise,它会参与后面的任务执行。现在我们根据上下文进入subscribe方法:

function subscribe(parent, child, onFulfillment, onRejection) { // parent = customPromiseObject, child = thenPromise, onFulfillment = 成功回调,onRejection = 失败回调
  var subscribers = parent._subscribers;
  var length = subscribers.length;

  subscribers[length] = child;
  subscribers[length + FULFILLED] = onFulfillment;
  subscribers[length + REJECTED]  = onRejection;
}

subscribers初始化是一个空数组,所以在这里执行完毕后,将会是以下效果:

  subscribers[0] = thenPromise;
  subscribers[1] = 成功回调;  
  subscribers[2] = 失败回调;
  promise._subscribers = subscribers;

由于我们的示例代码使用了catch,所以贴一下catch方法的源代码:

  'catch': function(onRejection) {
    return this.then(null, onRejection);
  }

所以在执行完then方法以及catch方法之后,promise._ subscribers内部如下:

  subscribers[0] = thenPromise;
  subscribers[1] = 成功回调;  
  subscribers[2] = 失败回调;  
  subscribers[3] = thenPromise2;
  subscribers[4] = null;  
  subscribers[5] = catch回调;

到这里,我们的同步事件就执行完了。接下来开始分析异步事件。我们回到lib/promise/asap.js文件。

在Promise早期的源代码中,对Promise的运行平台做了区分。我们这里对这块细节不做深究,但不管是哪个平台,最终还是会执行到flush方法。这里需要注意:在执行flush方法时,它已经在所有事件执行之后了。这里的所有事件指的是已经进入主线程队列的事件,也就是刚刚执行完成的同步事件。

function flush() {
  for (var i = 0; i < queue.length; i++) {
    var tuple = queue[i];
    var callback = tuple[0], arg = tuple[1];
    callback(arg);
  }
  queue = [];
}

flush方法在这里会回调刚刚传入的方法publishFulfillment,参数为那个promise对象。我们确认一下publishFulfillment方法的细节:

function publishFulfillment(promise) {
  publish(promise, promise._state = FULFILLED);
}

它这里很简单,直接调用了publish方法。不过在这里,promise对象的状态再一次被改变了。从PENDING -> SEALED -> FULFILLED。我们进入publish:

function publish(promise, settled) { // promise = new Promise, settled = FULFILLED
  var child, callback, subscribers = promise._subscribers, detail = promise._detail;

  for (var i = 0; i < subscribers.length; i += 3) { 
    child = subscribers[i];
    callback = subscribers[i + settled]; 

    invokeCallback(settled, child, callback, detail); 
  }

  promise._subscribers = null;
}

上面的代码开始Promise对象的观察者进行通知。注意这里的for循环只循环了两次。第一次循环:

  child == thenPromise;
  callback == 成功回调;

我们先来看正常的执行,这里直接调用了invokeCallback方法:

function invokeCallback(settled, promise, callback, detail) { settled = FULFILLED, promise = thenPromise, callback = 成功回调, detail = 'Hello'

  // 检测callback是否是一个方法
  var hasCallback = isFunction(callback),
      value, error, succeeded, failed;

  // 如果是方法,则执行
  if (hasCallback) {
    try {
      value = callback(detail);  
      succeeded = true;
    } catch(e) {
      failed = true;
      error = e;
    }
  } else {
    value = detail;
    succeeded = true;
  }

  if (handleThenable(promise, value)) {
    return;
  } else if (hasCallback && succeeded) {
    resolve(promise, value);
  } else if (failed) {
    reject(promise, error);
  } else if (settled === FULFILLED) {
    resolve(promise, value);
  } else if (settled === REJECTED) {
    reject(promise, value);
  }
}

咱们的示例比较简单,在检测回调方法是一个方法之后,就直接调用了。我们的示例也没有返回值。所以直接走进了hasCallback && succeeded的判断中,然后进入resolve方法。

function resolve(promise, value) { // promise = thenPromise, value = undefined
  if (promise === value) {
    fulfill(promise, value);
  } else if (!handleThenable(promise, value)) {
    fulfill(promise, value);
  }
}

根据上下文,这里进入了fulfill方法:

function fulfill(promise, value) {// promise = thenPromise, value = null 
  if (promise._state !== PENDING) { return; }
  promise._state = SEALED;// thenPromise._state = SEALED 
  promise._detail = value;// thenPromise._detail = null 

  config.async(publishFulfillment, promise);
}

到这里是不是似曾相识?没错,我们在上面已经见过上面两个方法。不过在这里是要通过flush回调执行thenPromise对象。然后根据上面提到的逻辑,最后thenPromise将会进入publish:

function publish(promise, settled) { // promise = thenPromise, settled = FULFILLED 
  var child, callback, subscribers = promise._subscribers, detail = promise._detail;

  for (var i = 0; i < subscribers.length; i += 3) { 
    child = subscribers[i];
    callback = subscribers[i + settled]; 

    invokeCallback(settled, child, callback, detail); 
  }

  promise._subscribers = null;
}

不过thenPromise对象是一个空对象,它的_subscribers属性是一个空数组。所以这里自然执行完毕。

不过到这里也才是thenPromise执行完毕,我们自己构造的Promise对象呢?它才准备进行第二次for循环:

  child == thenPromise2;
  callback == null;

然后根据上下文,在执行到invokeCallback方法,不过最终它的命运匹配到了settled === FULFILLED的条件,进入了resolve方法:

function resolve(promise, value) { // promise = new Promise, value = undefined
  if (promise === value) {
    fulfill(promise, value);
  } else if (!handleThenable(promise, value)) {
    fulfill(promise, value);
  }
}

再进入fulfill方法:

function fulfill(promise, value) {
  if (promise._state !== PENDING) { return; }
  promise._state = SEALED;
  promise._detail = value;

  config.async(publishFulfillment, promise);
}

不过执行到这里,promise的_state已经为SEALED了,不等于PENDING,所以Promise也就自然终结了。

至此,一个普通的Promise执行终结。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页