2

Promise源码学习(2)

本篇为上一篇源码学习(1)的补充,主要是来介绍Promise.all()和Promise.race()方法。
闲话少叙,进入正题

Promise.race()

首先来简单介绍一下功能吧,详细比如可见阮一峰老师的ES6书籍。
Promise.race方法是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
接下来贴源代码

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

  if (!isArray(entries)) {
    return new Constructor((_resolve, reject) => reject(new TypeError('You must pass an array to race.')));
  } else {
    return new Constructor((resolve, reject) => {//new 执行一次
      let length = entries.length;
      for (let i = 0; i < length; i++) {//执行每一个传入的entry 但只有最快的一个能resolve或reject改变返回的promise的状态
        Constructor.resolve(entries[i]).then(resolve, reject);
      }
    });
  }
}
//isArray定义:
if (Array.isArray) {
  _isArray = Array.isArray;
} else {
  _isArray = x => Object.prototype.toString.call(x) === '[object Array]';
}

如果传入的参数不是数据直接reject。
如果是数组,则依次resolve传入的thenable对象并在then中注册回调,设Promise.race()返回的新Promise的对象为p的话,最快执行完成的entry进入then回调,执行resolve或reject,以此来改变新对象p的状态。之后的entry完成在此执行resolve或reject均无效,因为Promise状态一旦确定无法改变,详见上篇关于fulfill()和reject()的注释和分析。

Promise.all()

基本介绍可见阮一峰老师的ES6书籍。
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
源码:

export default function all(entries) {
  return new Enumerator(this, entries).promise;//这里返回了一个新对象Promise
}

这里注意返回了一个新的Promise对象.
Enumerator源码如下

export default class 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;
      this._remaining = input.length;//未完成的promise总数量

      this._result = new Array(this.length);//每个promise结果

      if (this.length === 0) {
        fulfill(this.promise, this._result);
      } else {
        this.length = this.length || 0;
        this._enumerate(input);//处理输入的数组
        if (this._remaining === 0) {//都执行完毕
          fulfill(this.promise, this._result);
        }
      }
    } else {
      reject(this.promise, validationError());//传入不是array, reject it
    }
  }

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

  //处理所有的输入
  _eachEntry (entry, i) {
    let c = this._instanceConstructor;//Promise
    let {resolve} = c;//Promise.resolve

    if (resolve === originalResolve) {
      let then = getThen(entry);//获取then方法

      if (then === originalThen &&
        entry._state !== PENDING) {//如果entry已完成或已拒绝
        this._settledAt(entry._state, i, entry._result);
      } else if (typeof then !== 'function') {
        this._remaining--;//不是thenable 直接完成该entry
        this._result[i] = entry;
      } else if (c === Promise) {//不是promise但是一个thenable
        let promise = new c(noop);
        handleMaybeThenable(promise, entry, then);
        this._willSettleAt(promise, i);//暂时状态不确定,订阅之
      } else {
        this._willSettleAt(new c(resolve => resolve(entry)), i);
      }
    } else {
      this._willSettleAt(resolve(entry), i);
    }
  }

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

    if (promise._state === PENDING) {
      this._remaining--;//该entry状态已确定,待完成总数减一

      if (state === REJECTED) {
        reject(promise, value);//如果传入entry列表有一个rejected,立即设置promise结果rejected
      } else {
        this._result[i] = value;
      }
    }

    if (this._remaining === 0) {//全部处理完成fulfill
      fulfill(promise, this._result);
    }
  }

  _willSettleAt (promise, i) {
    let enumerator = this;
    //暂时状态不定,订阅之
    subscribe(
      promise, undefined,
      value => enumerator._settledAt(FULFILLED, i, value),//回调,设置promise状态
      reason => enumerator._settledAt(REJECTED, i, reason)
    );
  }
};

代码不是很多,在此就不逐个方法贴了。
首先看Constructor,细节不表,如果传入了一个thenable数组会在_enumerate方法中通过_eachEntry挨个处理,细节见注释。
总体思路就是对传入列表的元素挨个处理,该resolve则resolve,同时通过_remaining 对未完成的entry进行计数。
若entry是pending状态,则通过_willSettleAt来订阅,有确定结果时进行 _settledAt;
若entry已完成,直接_settledAt确定结果;
当_remaining === 0;也就是列表所有entry均已有结果,设置Promise.all()返回的新Promise对象的状态。
要注意,如果有一个entry被reject了,会直接设置 新Promise对象的状态为rejected。

总结

enter image description here
该图对Promise的流程总结。
总的来说,Promise通过链式语法使得异步操作更加的直观,避免了回调地狱的出现。使得代码更加易读可维护。
细节可见源码上的注释,全部代码可见es6-promise学习笔记

闲话

明后两天公司集体出游,没有大多的时间来打磨,可是自己又定了一个每周一篇学习总结小文章的目标,所以挤些时间提前写完这篇文章来完成目标吧。继续加油吧!


178096413
841 声望39 粉丝