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。
总结
该图对Promise的流程总结。
总的来说,Promise通过链式语法使得异步操作更加的直观,避免了回调地狱的出现。使得代码更加易读可维护。
细节可见源码上的注释,全部代码可见es6-promise学习笔记
闲话
明后两天公司集体出游,没有大多的时间来打磨,可是自己又定了一个每周一篇学习总结小文章的目标,所以挤些时间提前写完这篇文章来完成目标吧。继续加油吧!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。