Promise源码解析

Posted Sahadev_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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执行终结。

以上是关于Promise源码解析的主要内容,如果未能解决你的问题,请参考以下文章

从源码看 Promise 概念与实现

SynchronousQueue 1.8 源码解析

spring源码学习之springMVC

Axios使用及源码解析

[JS]源码学习Promise

[JS]源码学习Promise