es6-promise 源码分析

140 阅读8分钟

1. PROMISE_ID

当创建 Promise 的时候就会生成一个 PROMISE_ID ,目前还不知道是干啥的,生成的规则:

export const PROMISE_ID = Math.random().toString(36).substring(2);

2. Promise 的成员变量

当创建 Promise 对象的时候会初始化三个变量。

  • _result 保存 Promise 的结果信息;
  • _state 保存 Promise 的状态信息;
  • _subscribers 保存 Promise 的相关回调。

2.1 _result

保存 Promise 执行的结果。

2.2 _state

Promise 里面有三个状态:

  • PENDING 代表正在执行中;
  • FULFILLED 已经成功执行;
  • REJECTED 执行完成,但是出现了错误。

这三个所对应的真实值分别为:PENDING(void 0);FULFILLED(1);REJECTED(2)。整个 Promise 执行的过程中会经历,当没有出现错误的情况( PENDING -> FULFILLED);当出现错误的情况( PENDING -> REJECTED )。

2.3 _subscribers

这里面保存的是要执行的函数,也就是 .then 传递的回调函数,函数声明:

declare function then(onFulfillment: (result: any) => any, onRejection: (error: any) => any): Promise

只不过除了上面的两个函数外,还有一个空的函数,源码中写的是 noop

function noop() {}

当这些函数保存以后得到的 _subscribers 数组为:

[  noop,  onFulfillment,  onRejection]

3. Promise 的初始化

constructor(resolver) {
  this[PROMISE_ID] = nextId();

  this._result = this._state = undefined;
  this._subscribers = [];

  if (noop !== resolver) {
    typeof resolver !== 'function' && needsResolver();
    this instanceof Promise ? initializePromise(this, resolver) : needsNew();
  }
}

可以看到主要是初始化成员变量;校验传入的参数 resolver 除此之外的重点就是 initializePromise 了。

既然说到这里了,就根据代码说说传入参数的说明:

传入的 resolver 必须是一个函数,且不能是 noop 函数;回调不能是 Promise 。我们知道有时候我们有在回调里面调用异步方法的,这个时候我们不应该尝试传入异步的回调方法,而应该是在回调里面通过下面的方式调用异步方法:

// 错误的做法
new Promise(new Promise((resolve, reject) => {
  // 做相关的逻辑
}))

// 正确的做法
new Promise((resolve, reject) => {
  new Promise((resolve, reject) => {
    // 做相关逻辑
  }).then((value) => {
    // 这里拿到值做相关逻辑
  })
})

4. Promise 的任务队列

在这个源码里面有任务队列的,专门负责执行 Promise 里面的操作。初始化的地方:

const queue = new Array(1000);

每个任务占两个元素的位置:

  • 要执行的任务;
  • 当前执行任务的 Promise

下面我们来看看消费任务队列的函数:

function flush() {
  for (let i = 0; i < len; i+=2) {
    let callback = queue[i];
    let arg = queue[i+1];

    callback(arg);

    queue[i] = undefined;
    queue[i+1] = undefined;
  }

  len = 0;
}

记住每次循环拿到的 callbackarg ,下面会详细讲到。我们看到就是循环上面的队列,等全部完成后,重置队列的下标 len = 0。至于是什么负责执行这个消费函数,具体要看你的执行函数,如果是 nodejs ,那么就使用:

process.nextTick(flush)

这里的 flush 就是上面的 flush 函数,非常的直观和清晰。至于其他的,对应的代码如下:

let scheduleFlush;
// Decide what async method to use to triggering processing of queued callbacks:
if (isNode) {
  scheduleFlush = useNextTick();
} else if (BrowserMutationObserver) {
  scheduleFlush = useMutationObserver();
} else if (isWorker) {
  scheduleFlush = useMessageChannel();
} else if (browserWindow === undefined && typeof require === 'function') {
  scheduleFlush = attemptVertx();
} else {
  scheduleFlush = useSetTimeout();
}

现在我们再回过头看一下队列里面的数据到底是怎样保存的;首先我们先看队列中的 callback 里面是啥?

function publish(promise) {
  let subscribers = promise._subscribers;
  let settled = promise._state;

  if (subscribers.length === 0) { return; }

  let child, callback, detail = promise._result;

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

    if (child) {
      invokeCallback(settled, child, callback, detail);
    } else {
      callback(detail);
    }
  }

  promise._subscribers.length = 0;
}

publish 函数的参数就是 Promise 本身,只不过在这里由于不是 Promise 类的一部分,所以需要传递过来,而这个参数也就是队列里面的 arg 。可以看到函数就是将 Promise 中保存的 _subscribers 消费掉;消费原则就是根据 _state 属性;这里为啥会有循环呢??这个结合后面我们调用实例的时候会具体说,这里只说明一下,是因为使用者可能会有多个 .then 方法,所以存在循环。下面我们看每次循环拿到的 childcallback 这两个变量。结合上面我们说的 _subscribers 保存的值:

[child, onFulfillment, onRejection, child, onFulfillment, onRejection, ...]

其中这里的 callback 函数取决于 _state 的值,然后它的取值分别是 undefined,1,2,如果此时 Promise 的状态是 1 也就是 FULFILLED ,那么取到到的值就是 onFulfillment ;如果是 2 ,那么取到的值则为: onRejection

5. 实例化 Promise

const delay1000 = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('------delay1000')
    resolve()
  }, 1000)
})

这个时候什么都不操作仍然能打印上面的日志,这是因为当你调用 new Promise 的时候就已经开始执行你传入的 resolver 回调函数了。

function initializePromise(promise, resolver) {
  try {
    resolver(function resolvePromise(value){
      resolve(promise, value);
    }, function rejectPromise(reason) {
      reject(promise, reason);
    });
  } catch(e) {
    reject(promise, e);
  }
}

这个函数就是在 Promise 构造函数里面执行的,而 Promise 回调给我们的就是:

// resolve
function resolvePromise(value){
  resolve(promise, value);
}

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

所以当我们调用 resolvereject 的时候实际上是调用里面的 resolve(promise, value)reject(promise, reason)。而这两个函数的目的就是将结果数据保存起来,我们首先看 resolve

function resolve(promise, value) {
  // 如果返回的值是本身,那么直接调用 reject
  if (promise === value) {
    reject(promise, selfFulfillment());
    // 如果是对象或者方法
  } else if (objectOrFunction(value)) {
    let then;
    try {
      then = value.then;
    } catch (error) {
      reject(promise, error);
      return;
    }
    handleMaybeThenable(promise, value, then);
    // 如果只是普通的值,那么就走这里,像上面的例子就走这里
  } else {
    fulfill(promise, value);
  }
}

上面我们传入的值是 undefined ,也就是 valueundefined 。然后我们接着看 fulfill 函数:

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

  promise._result = value;
  promise._state = FULFILLED;

  if (promise._subscribers.length !== 0) {
    asap(publish, promise);
  }
}

当我们只是创建 Promise 的时候,也就是还没有执行 .then 方法,那么 _state 的值永远都是 PENDING ;所以我们上面的示例执行到这里的时候这个条件是不满足的,就会执行到下面的代码,也就是保存执行的结果和更新当前 Promise 状态,也就是从 PENDING -> FULFILLED ;而 promise._subscribers 的长度对于我们的示例来说就是 0asap 这个函数就是将任务加入到队列中,但此时不会执行。

6. .then 操作

Promise 的结果正常是通过 .then 来拿到执行的结果。

function then(onFulfillment, onRejection) {
  const parent = this;

  const child = new this.constructor(noop);

  if (child[PROMISE_ID] === undefined) {
    makePromise(child);
  }

  const { _state } = parent;
	// 如果在执行 Promise 的时候状态是 FULFILLED 或 REJECTED
  if (_state) {
    const callback = arguments[_state - 1];
    asap(() => invokeCallback(_state, child, callback, parent._result));
  } else {
    // 如果状态是 PENDING ,那么就把相关参数保存起来
    subscribe(parent, child, onFulfillment, onRejection);
  }

  return child;
}

看到这里逻辑就清晰起来了。下面来看一下流程图(不是很规范),其中循环队列,其实就是循环上面说的数组。

还有一个流程没有画进去,也就是当 .then 方法返回的还是 Promise 的时候,我们先来看看代码:

function handleMaybeThenable(promise, maybeThenable, then) {
  // 如果返回的是 Promise 并且是这个 Promise
  if (maybeThenable.constructor === promise.constructor &&
      then === originalThen &&
      maybeThenable.constructor.resolve === originalResolve) {
    handleOwnThenable(promise, maybeThenable);
  } else {
    if (then === undefined) {
      fulfill(promise, maybeThenable);
      // 如果是一个方法,也就是当这个是别人开发的 Promise
    } else if (isFunction(then)) {
      handleForeignThenable(promise, maybeThenable, then);
    } else {
      fulfill(promise, maybeThenable);
    }
  }
}

从这个代码我们知道,只要是 Promise ,那么是可以相互之间使用的,比如你即使用了这个库的 Promise ,也使用了其他库的,这样是可以的。下面先看如果仍然返回的是这个库的 Promise ,那么逻辑怎么处理:

function handleOwnThenable(promise, thenable) {
  if (thenable._state === FULFILLED) {
    fulfill(promise, thenable._result);
  } else if (thenable._state === REJECTED) {
    reject(promise, thenable._result);
  } else {
    subscribe(thenable, undefined, value  => resolve(promise, value),
                                   reason => reject(promise, reason))
  }
}

如果状态是完成状态( FULFILLEDREJECTED ),那么就直接返回,否则就保存起来。如果是用别人开发的 Promise ,那么就这样处理逻辑:

function handleForeignThenable(promise, thenable, then) {
   asap(promise => {
    let sealed = false;
    let error = tryThen(then, thenable, value => {
      if (sealed) { return; }
      sealed = true;
      if (thenable !== value) {
        resolve(promise, value);
      } else {
        fulfill(promise, value);
      }
    }, reason => {
      if (sealed) { return; }
      sealed = true;

      reject(promise, reason);
    }, 'Settle: ' + (promise._label || ' unknown promise'));

    if (!sealed && error) {
      sealed = true;
      reject(promise, error);
    }
  }, promise);
}

使用的时候我们知道,我们返回的 Promise ,是将结果给下一个 .then 的回调,而 .then 的回调是保存在 _subscribers 参数中,那么这里要做的就是将其他库中的结果去消化我们库的 _subscribers 。所以可以看到传入的回调中执行了 resolve ,而这个函数就会去消化 _subscribers 。其中 tryThen 就是执行其他库的 .then 方法:

function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
  try {
    then.call(value, fulfillmentHandler, rejectionHandler);
  } catch(e) {
    return e;
  }
}

7. all

all 会等传入的全部的 Promise 都执行完成了才返回。

// a, b, c, d Promise 的实例化
Promise.all([a, b, c, d]).then(_result => {
  // 要等到 a, b, c, d 这四个异步任务都完成才走到这里
})

下面我们看源码,先看实例化方法:

export default function all(entries) {
  return new Enumerator(this, entries).promise;
}

就是去实例化 Enumerator 类,然后我们接着看 Enumerator 类的构造方法。

constructor(Constructor, input) {
  this._instanceConstructor = Constructor;
  // 给自己实例化一个 Promise 作为自己的局部变量
  // 也就是把整个 all 当成是一个 Promise,这样就可以整体返回了
  this.promise = new Constructor(noop);

  if (!this.promise[PROMISE_ID]) {
    makePromise(this.promise);
  }

  if (isArray(input)) {
    // 总的长度
    this.length     = input.length;
    // 剩余长度,没有执行完成的 Promise 长度
    this._remaining = input.length;
  	// 保存传入数组的 Promise 执行结果
    this._result = new Array(this.length);

    if (this.length === 0) {
      // 如果是空数组,那么就直接返回
      fulfill(this.promise, this._result);
    } else {
      this.length = this.length || 0;
      this._enumerate(input);
      // <1>如果剩余量都空了,那就代表执行完成了,直接返回
      if (this._remaining === 0) {
        fulfill(this.promise, this._result);
      }
    }
  } else {
    // 如果传入得到不是数组,那么直接报错
    reject(this.promise, validationError());
  }
}

其中 <1> 这个地方为啥会执行完成呢,上面 Promise 的代码如果看明白的话,我们知道 Promise 实例化的时候就已经开始执行 Promise 了,如果当执行 all 的时候完全有可能全部都执行完成了。不管有没有执行完成这里我们都得看 _enumerate 这个函数,因为 _remaining 的长度值是在这里面减的。

_enumerate(input) {
  for (let i = 0; this._state === PENDING && i < input.length; i++) {
    this._eachEntry(input[i], i);
  }
}

这个函数似乎很简单,就是循环 Promise 数组。所以还是具体看 _eachEntry 函数。

_eachEntry(entry, i) {
  let c = this._instanceConstructor;
  let { resolve } = c;
  // 这是判断 Promise 的 resolve 是不是这个库自己的 resolve
  if (resolve === originalResolve) {
    let then;
    let error;
    let didError = false;
    try {
      // 看一下 then 方法
      then = entry.then;
    } catch (e) {
      didError = true;
      error = e;
    }
  	// then 方法是不是这个库自己的 then 方法
    if (then === originalThen &&
      entry._state !== PENDING) {
      // 当状态已经完成,执行这个
      this._settledAt(entry._state, i, entry._result);
    } else if (typeof then !== 'function') {
      // 如果 then 不是方法,那么直接把传入的值当做结果,同时维护 _remaining 变量
      this._remaining--;
      this._result[i] = entry;
    } else if (c === Promise) {
      let promise = new c(noop);
      if (didError) {
        reject(promise, error);
      } else {
        handleMaybeThenable(promise, entry, then);
      }
      this._willSettleAt(promise, i);
    } else {
      this._willSettleAt(new c(resolve => resolve(entry)), i);
    }
  } else {
    this._willSettleAt(resolve(entry), i);
  }
}

我们首先看一下,当执行这个函数状态就完成的情况,也就是会执行 _settledAt 方法。

_settledAt(state, i, value) {
  let { promise } = this;

  if (promise._state === PENDING) {
    // 维护剩余长度 _remaining 
    this._remaining--;

    if (state === REJECTED) {
      // 如果是错误抛出异常
      reject(promise, value);
    } else {
      // 如果是正确的结果就直接保存
      this._result[i] = value;
    }
  }
  // 如果剩余长度为 0 ,那整体完成
  if (this._remaining === 0) {
    fulfill(promise, this._result);
  }
}

就是保存一下结果,同时看一下是不是 all 都完成了,如果完成了就整体都完成,否则就执行其他的。

8. resolve 和 reject

这个比较简单,就是将传入的值转换成 Promise ,如果本身就是 Promise ,那么就保持原样。

function resolve(object) {
  /*jshint validthis:true */
  let Constructor = this;

  if (object && typeof object === 'object' && object.constructor === Constructor) {
    return object;
  }

  let promise = new Constructor(noop);
  _resolve(promise, object);
  return promise;
}

_resolve 这个函数上面说过,其实就是判断传入的值是啥,如果是值就直接保存起来,如果是 Promise 就保存 .then 的回调。这里的值都是保存到 resolve 回调中; reject 则是保存到 reject 回调中。

function reject(reason) {
  /*jshint validthis:true */
  let Constructor = this;
  let promise = new Constructor(noop);
  _reject(promise, reason);
  return promise;
}

9. race

这个我们知道传入的 Promise 或者值中,谁先完成就返回谁。

function race(entries) {
  /*jshint validthis:true */
  let Constructor = this;

  if (!isArray(entries)) {
    return new Constructor((_, reject) => reject(new TypeError('You must pass an array to race.')));
  } else {
    return new Constructor((resolve, reject) => {
      let length = entries.length;
      for (let i = 0; i < length; i++) {
        Constructor.resolve(entries[i]).then(resolve, reject);
      }
    });
  }
}

看源码很清楚就是把传入的数组通过 Promise.resolve 转换成 Promise ,然后直接传入回调,而且是同一个,所以谁先完成这个 Promise 就完成。