目录
代码分析(基于 v4.2.8+1e68dce6 版本)
- 前言
- 了解一些术语、规范
- 构造函数
- Promise 是同步执行还是异步执行
- 从创建对象开始
- 同步的函数什么时候执行
- resolve、reject 从哪里来
- 状态变化
- Promise 的三种状态
- Pending->Fulfilled
- Pending->Rejected
- 问答
- then
- 返回新的 Promise 对象
- 异步回调
- invokeCallback 函数
- 补充
- 示例解析
- 问答
- all
- 构造函数做了什么
- 顺序执行
- 其它
- resolve
- reject
- catch
- race
- 总结
前言
了解一些术语、规范
请参考Promises/A+规范 www.ituring.com.cn/article/665…
源码地址 cdn.jsdelivr.net/npm/es6-pro…
构造函数
Promise 是同步执行还是异步执行
对于这种问题我们还是举个例子吧.
var p = new Promise(function executor(resolve, reject) {
console.log(1);
resolve();
}).then(() => {
console.log(2);
});
console.log(3);
// 1
// 3
// 2
由此看来创建对象的 executor 函数是同步执行,而 then 的回调函数是异步执行
js 代码自上而下执行对于输出结果或许认为应该是 1、2、3 或许知道是 1、3、2,但为什么 executor函数内是同步输出(又是什么时候执行的),而 then 的回调函数却是异步执行?
带着疑问让我们一步一步的了解一下源码内的执行机制,看能否得出答案.
从创建对象开始
//源码
function needsResolver() {
throw new TypeError(
'You must pass a resolver function as the first argument to the promise constructor'
);
}
function needsNew() {
throw new TypeError(
"Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."
);
}
function Promise(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,并且参数类型必须为 Function 否则抛错typeof resolver !== 'function' && needsResolver(). - 初始化属性
_result_state_subscribers值. - 判断 this 是否来源于 Promise 构造函数(通过 new Promise() 创建的),如果是则调用函数
initializePromise(this, resolver).
这里需要注意两点:
-
初始化
_subscribers属性,说明 Promise 使用了发布/订阅者模式,间接的证明 Promise 是有异步操作(触发事件->异步消息队列->品->仔细品->...). -
调用
initializePromise函数,传递的参数this(this=new Promise()对象始终贯穿全文),resolver(构造函数接收的参数).
因为构造函数接收的参数resolver是一个可执行函数,但在刚才的构造函数源码内并没有发现立即执行该函数,而是把它作为参数传给了initializePromise函数,所以我们接下来重点看一下initializePromise会做哪些操作.
同步的函数什么时候执行
// 源码
function initializePromise(promise, resolver) {
try {
resolver(
function resolvePromise(value) {
resolve(promise, value);
},
function rejectPromise(reason) {
reject(promise, reason);
}
);
} catch (e) {
reject(promise, e);
}
}
想必看到这个函数源码的时候,对于文章开头提到的executor函数既然是同步输出那什么时候执行心中已经有了答案.(应该明白 为什么首先输出1)
是的你没看错,initializePromise(promise,resolver)该函数接收的参数resolver就是创建对象时传进去的参数var p=new Promise(function executor(){});,executor函数就是在此立即执行了.
至此我们已经得出了一个答案(重要的事情说三遍)
创建对象时,构造函数接收的参数(类型 Function)是同步执行的.
创建对象时,构造函数接收的参数(类型 Function)是同步执行的.
创建对象时,构造函数接收的参数(类型 Function)是同步执行的.
var p1 = new Promise(function executor() {}); //executor函数作为参数
// or
var p2 = new Promise(() => {}); //箭头函数作为参数
如上面的示例代码,function executor() {},() => {}在创建对象时,会立即执行.
resolve、reject 从哪里来
再次回顾initializePromise函数,发现执行resolver函数的时候会传递 2 个参数(类型均为 Function)
resolver(function resolvePromise(){},function rejectPromise(){}),如果对这种写法有一点点不太明白的话,下面示例代码我们换一种写法.
/**
*
* @param {Function} resolver 创建对象时接收一个参数,类型Function
*/
function Promise(resolver) {
this._result = this._state = undefined;
this._subscribers = [];
initializePromise(this, resolver);
}
/**
*
* @param {Promise} promise
* @param {Function} resolver
*/
function initializePromise(promise, resolver) {
resolver(resolvePromise, rejectPromise);
}
function resolvePromise(value) {
console.log('resolvePromise', value);
}
function rejectPromise(value) {
console.log('rejectPromise', value);
}
(function test() {
var p1 = new Promise(function executor() {
console.log('1');
});
//or
var p2 = new Promise(() => {
console.log('2');
});
})();
以上的写法是常见的函数定义、调用的写法,方便大家理解.
我们继续往下看,调用resolver(resolvePromise,rejectPromise)函数时传递的 2 个参数resolvePromise rejectPromise,传到哪里,又再什么时候执行.
这个问题如果认真看过前言部分或了解 Promise 规范的话应该知道答案了.
上面我们提到过resolver函数其实就是创建对象时传入的executor函数,理所当然resolver(resolvePromise,rejectPromise)=executor(resolvePromise,rejectPromise).
var p1 = new Promise(function executor(resolve, reject) {}); //具名函数executor
// or
var p2 = new Promise((resolve, reject) => {}); //箭头函数
如上面的示例代码,(形)参数resolve reject就是源码内对外抛出的resolvePromise rejectPromise的两个函数.
这里的var p1 = new Promise(function executor(resolve, reject) {});,至于形参的名字,也可以使用其它代替如 var p1=new Promise(funciton(a,b){}),只要知道 a,b 代表的是什么.不过最好还是像规范一样语义化.
何时触发resolve reject函数呢?这个就相对简单了,控制权已经交给你了.
到目前为止,我们总结了以下几点:
- 创建对象时传递的参数(类型 Function)是同步执行.
resolvereject的由来.- 上面似乎啰嗦了很多...
状态变化
var p1 = new Promise(function executor(resolve, reject) {
resolve();
});
var p2 = new Promise(function executor(resolve, reject) {
reject();
});
在构造函数一节中已经知道了resolve reject函数的由来,但有没有思考过这两个函数是做什么用的?带着疑问我们从本节寻找答案.
Promise 的三种状态
大家看过规范或使用过 Promsie 的应该知道它有三种状态
- 创建对象(初始化)时的等待状态
Pending.(es6-promise 源码内 undefined 表示Pending) - 成功(解决)时的状态
Fulfilled.(es6-promise 源码内 1 表示Fulfilled) - 失败(拒绝)时的状态
Rejected.(es6-promise 源码内 2 表示Rejected)
对于状态可能有其它描述术语这里不做过多了解(只需要记住有三种状态即可),既然有状态的存在不妨把它理解为状态机(基于状态变化进行的操作). 接下来我们来看一下状态之间是如何变化的.
Pending->Fulfilled
// 源码
function initializePromise(promise, resolver) {
try {
resolver(
function resolvePromise(value) {
resolve(promise, value);
},
function rejectPromise(reason) {
reject(promise, reason);
}
);
} catch (e) {
reject(promise, e);
}
}
function resolve(promise, value) {
if (promise === value) {
reject(promise, selfFulfillment());
} else if (objectOrFunction(value)) {
var then$$1 = void 0;
try {
then$$1 = value.then;
} catch (error) {
reject(promise, error);
return;
}
handleMaybeThenable(promise, value, then$$1);
} else {
fulfill(promise, value);
}
}
function fulfill(promise, value) {
if (promise._state !== PENDING) {
return;
}
promise._result = value;
promise._state = FULFILLED;
if (promise._subscribers.length !== 0) {
asap(publish, promise);
}
}
继上一节的源码继续往下看在resolvePromise函数内直接调用了resolve(promise, value)函数,最后在fulfill函数体内看到了对 Promsie 对象的状态进行了修改promise._state = FULFILLED.
至此我们对本节开头提到的resolve是做什么的有了答案.
resolve函数主要是改变状态从 Pending->Fulfilled
Pending->Rejected
// 源码
function initializePromise(promise, resolver) {
try {
resolver(
function resolvePromise(value) {
resolve(promise, value);
},
function rejectPromise(reason) {
reject(promise, reason);
}
);
} catch (e) {
reject(promise, e);
}
}
function reject(promise, reason) {
if (promise._state !== PENDING) {
return;
}
promise._state = REJECTED;
promise._result = reason;
asap(publishRejection, promise);
}
同理我们在rejectPromise函数内直接调用了reject(promise, value)函数,同样是对 Promsie 对象的状态进行了修改promise._state = REJECTED.
对开头提到的另一个问题reject是做什么的想必也知道了.
reject函数主要是改变状态从 Pending->REJECTED
了解了状态是如何变化的看以下问题是否知道答案.
问答
状态是否会发生多次改变
var p = new Promise((resolve, reject) => {
resolve(); //第一次改变状态
reject(); //第二次改变状态
});
console.log(p);
直接复制放到 chrome 控制台查看结果状态resolved,如果在 es6-promise 的源码中输出结果 1 表示FULFILLED(其实和resolved是一个意思).
//源码
function fulfill(promise, value) {
if (promise._state !== PENDING) {
return;
}
}
function reject(promise, reason) {
if (promise._state !== PENDING) {
return;
}
}
为什么reject()第二次改变状态没发生变化呢?让我们重新看一下上面的源码无论是fulfill reject函数都会先判断当前状态,如果状态是PENDING才继续执行,所以要记住一点:
状态只会是从 Pending->Fulfilled或Pending->Rejected改变一次且不可逆.
重要的事情说三遍:
状态只会改变一次
状态只会改变一次
状态只会改变一次
创建完了对象、状态也了解,状态发生改变之后的事情呢?是不是该 then 方法出场了...
返回新的 Promise 对象
function then(onFulfillment, onRejection) {
var parent = this;
var child = new this.constructor(noop);
if (child[PROMISE_ID] === undefined) {
makePromise(child);
}
var _state = parent._state;
if (_state) {
var callback = arguments[_state - 1];
asap(function () {
return invokeCallback(_state, child, callback, parent._result);
});
} else {
subscribe(parent, child, onFulfillment, onRejection);
}
return child;
}
根据规范,then 方法必须返回一个新的promise对象,所以源码then方法第一步便是通过new this.constructor(noop)构造函数创建一个新的对象.
异步回调
我们再回头查看then方法的源码该方法接收两个参数onFulfillment onRejection结合上一节讲的promise有两种状态变化Pending->Fulfilled或Pending->Rejected,所以这两个参数(函数)用来处理状态变化之后的事情.
onFulfillment函数处理Fulfilled状态之后的事情.onRejection函数处理Rejected状态之后的事情.
了解了两个参数之后,我们看传入的两个参数(回调函数什么时候触发).源码里首先对状态进行了判断if(_state)...else,为什么会对状态进行判断,难道不是直接调用onFulfillment或onRejection函数就可以了.
上面对两个onFulfillment onRejection函数进行了描述(状态发生变化之后调用对应的函数),如果状态是Pending进入then方法该如何处理这种情况的存在(这就是为什么存在if...else).
function onFulfillment(val) {
console.log(val);
}
function onRejection(error) {
console.log(error);
}
var p = new Promise((resolve, reject) => {
//构造函数创建对象,此时的状态是Pending
//此处用setTimeout模拟调用后端接口5秒之后返回结果(5秒之后改变状态)
setTimeout(() => {
resolve({ name: 'zhangsan' }); //得到数据,改变状态Pending->Fulfilled
//or
// reject(new Error('接口无响应')); //请求失败,改变状态Pending->Rejected
}, 5000);
}).then(onFulfillment, onRejection);
以上代码示例模拟Pending状态进入then方法,代码自上而下执行创建对象,发起调用后端接口请求等待响应的同时,代码继续执行调用then方法时,所以进入then方法内会对状态做判断,我们接着看else内如何处理Pending状态.
//源码
else {
subscribe(parent, child, onFulfillment, onRejection);
}
function subscribe(parent, child, onFulfillment, onRejection) {
var _subscribers = parent._subscribers;
var length = _subscribers.length;
parent._onerror = null;
_subscribers[length] = child;
_subscribers[length + FULFILLED] = onFulfillment;
_subscribers[length + REJECTED] = onRejection;
if (length === 0 && parent._state) {
asap(publish, parent);
}
}
在构造函数一节提到过的发布/订阅这里终于出现了,else内对尚处于Pending状态的情况直接存入订阅队列(等待状态改变触发).
对于构造函数一节开头提到的 then 的回调函数却是异步执行,这里应该可以得到答案了.
既然else里对Pending状态做了存入订阅队列的操作,那if里面应该是对Fulfilled Rejected状态做的处理吧.
什么情况会进入到if里?(创建对象之后,立刻改变状态)
function onFulfillment(val) {
console.log(val);
}
function onRejection(error) {
console.log(error);
}
var p = new Promise((resolve, reject) => {
//构造函数创建对象,此时的状态是Pending
//立刻改变状态
resolve(1); //改变状态Pending->Fulfilled
//or
// reject(new Error('error')); //改变状态Pending->Rejected
}).then(onFulfillment, onRejection);
上面示例代码模拟创建对象之后,立刻调用resolve 或reject函数改变初始化状态.我们还是看源码if内如何处理吧.
// 源码
if (_state) {
var callback = arguments[_state - 1];
asap(function () {
return invokeCallback(_state, child, callback, parent._result);
});
}
由于if内只确定状态发生了变化,至于是从Pending->Fulfilled 还是 Pending->Rejected则是通过_state状态码确认,所以这里使用arguments获取状态对应的回调函数.
/**
*
* @param {Function} resolver 创建对象时接收一个参数,类型Function
*/
function Promise(resolver) {
this._result = this._state = undefined;
this._subscribers = [];
initializePromise(this, resolver);
}
Promise.prototype.then = function (onFulfillment, onRejection) {
//判断状态
var _state = this._state;
if (_state) {
//通过状态码,获取对应的回调函数
var callback = arguments[_state - 1];
console.log('当前状态:', this._state, '对应的回调函数:', callback);
} else {
console.log('当前状态:', this._state, '存入队列');
}
};
/**
*
* @param {Promise} promise
* @param {Function} resolver
*/
function initializePromise(promise, resolver) {
resolver(
(value) => {
resolvePromise(promise, value);
},
(value) => {
rejectPromise(promise, value);
}
);
}
function resolvePromise(promise, value) {
promise._state = 1;
console.log('resolvePromise', value, '改变状态:', promise._state);
}
function rejectPromise(promise, value) {
promise._state = 2;
console.log('rejectPromise', value, '改变状态:', promise._state);
}
function onFulfillment(value) {
console.log('Fulfilled', value);
}
function onRejection(error) {
console.log('Fulfilled', error);
}
(function test() {
new Promise((resolve, reject) => {
console.log('创建对象');
//立刻改变状态
resolve();
}).then(onFulfillment, onRejection);
})();
以上示例模拟了arguments的使用.(或自行检索使用方法)
到目前为止,then创建了promise新对象,通过arguments获取到当前状态对应的回调函数,接下来是否会立即触发回调函数我们继续看源码.
//源码
if (_state) {
var callback = arguments[_state - 1];
asap(function () {
return invokeCallback(_state, child, callback, parent._result);
});
}
var len = 0;
var queue = new Array(1000);
var customSchedulerFn = void 0;
var scheduleFlush = void 0;
function asap(callback, arg) {
queue[len] = callback;
queue[len + 1] = arg;
len += 2;
if (len === 2) {
// If len is 2, 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.
if (customSchedulerFn) {
customSchedulerFn(flush);
} else {
scheduleFlush();
}
}
}
function setScheduler(scheduleFn) {
customSchedulerFn = scheduleFn;
}
// 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();
}
asap = as soon as possible 越快越好
invokeCallback= 调用回调
看到这些函数名距离....调用...回调函数..不远了所以细品,细细品接下来的处理流程.
首先看asap函数做了什么.
- 函数接收两个参数
- callback 回调函数
- arg 参数
- 参数存入队列
- 判断 len 长度
- 这里判断
len===2的原因在于初始化queue数组长度为 1000 但值为undefined,将参数存入数组并判断长度只为确定数组内有值(可立即执行的函数).
- 这里判断
- customSchedulerFn
- 对开发者提供了
setScheduler方法对customSchedulerFn赋值实现定制化调用队列,(假如在此次准备开始调用队列之前还有其它事情要处理完成之后才可以调用,则可以实现customSchedulerFn函数)这里不做过多说明.
- 对开发者提供了
- scheduleFlush
- 这个函数针对不同的执行环境(Node、浏览器)等做了处理,由于我们在浏览器端执行,所以直接看
useMutationObserver函数.
- 这个函数针对不同的执行环境(Node、浏览器)等做了处理,由于我们在浏览器端执行,所以直接看
//源码
function useMutationObserver() {
var iterations = 0;
var observer = new BrowserMutationObserver(flush);
var node = document.createTextNode('');
observer.observe(node, { characterData: true });
return function () {
node.data = iterations = ++iterations % 2;
};
}
BrowserMutationObserver使用参考 developer.mozilla.org/zh-CN/docs/…
useMutationObserver函数做了什么.
- 初始化对象的
new BrowserMutationObserver(flush)的flush参数和asap函数内调用customSchedulerFn(flush)函数传入的flush是一样的.(无论是开发者实现customSchedulerFn函数还是按计划调用的scheduleFlush函数最后一步都是调用flush函数).
最后调用的flush函数又做了什么.
//源码
function flush() {
for (var i = 0; i < len; i += 2) {
var callback = queue[i];
var arg = queue[i + 1];
callback(arg);
queue[i] = undefined;
queue[i + 1] = undefined;
}
len = 0;
}
- flush 函数内执行
queue队列上的各个回调函数 - 清空队列,重置 len=0
invokeCallback 函数
//源码
function invokeCallback(settled, promise, callback, detail) {
var hasCallback = isFunction(callback),
value = void 0,
error = void 0,
succeeded = true;
if (hasCallback) {
try {
value = callback(detail);
} catch (e) {
succeeded = false;
error = e;
}
if (promise === value) {
reject(promise, cannotReturnOwn());
return;
}
} else {
value = detail;
}
if (promise._state !== PENDING) {
// noop
} else if (hasCallback && succeeded) {
resolve(promise, value);
} else if (succeeded === false) {
reject(promise, error);
} else if (settled === FULFILLED) {
fulfill(promise, value);
} else if (settled === REJECTED) {
reject(promise, value);
}
}
之所以把invokeCallback函数放到最后讲解,主要是根据代码执行顺序而已(如果不了解invokeCallback函数是怎么被调用的,请再看一下本节关于异步回调部分的讲解).
invokeCallback 函数做了什么
-
参数部分
settled当前状态promisepromise 对象(指then方法内创建的 promise 子对象)callback回调函数(指then方法的参数onFulfillment或onRejection函数)detail传递的 value 值
-
callback类型判断- Function 类型则执行函数获取返回值
value = callback(detail) - 非 Functoin 类型则直接做赋值操作
else{value = detail}
- Function 类型则执行函数获取返回值
-
对新(子)
promise对象的操作- 根据不同的判断条件调用对应的方法(
resolverejectfulfill)修改新(子)promise对象的状态
- 根据不同的判断条件调用对应的方法(
以上就是对then方法内的操作理解(如果有不明白的先多看几次整个过程).
补充
这里有必要再次说一下then方法内的执行步骤
- 创建新(子)
promsie对象 - 判断状态
Pending状态通过调用subscribe函数把回调函数存入_subscribers订阅队列,等待状态发生改变触发发布publish函数Fulfilled或Rejected状态通过调用asap函数把回调函数存入queue队列等待立刻执行(循环队列内的回调函数)
- 返回新(子)
promsie对象
可以理解promise有两个队列
- 立刻执行队列-状态是
Fulfilled或Rejected的回调函数存入此队列 - 订阅队列-状态是
Pending的回调函数存入此队列
示例解析
以下示例Promise构造函数来源于 es6-promise.js 请与浏览器内置Promise区分.
示例一 演示立刻执行队列的过程(初始化对象,立刻改变状态)
new ES6Promise((resolve, reject) => {
console.log('init.');
resolve(0); //状态Pending->Fulfilled
}).then(
(value) => {
//由于构造函数内状态立刻发生了变化,此回调函数存入立刻执行队列
console.log('接收到的值为:', value);
return value + 1;
},
(error) => {
return new Error('msg');
}
);
// init.
// 接收到的值为:0
执行过程解析:
- 1.创建对象,输出
init. - 2.立刻调用
resolve方法- 2.1
resolve源码内改变状态
- 2.1
- 3.调用
then方法- 3.1
then源码内创建新(子)promise对象 - 3.2 判断当前状态为
Fulfilled,立刻调用asap方法将回调方法存入队列,等待立刻执行.
- 3.1
- 4.
then方法返回新(子)promise对象
示例二 演示订阅队列的过程(初始化对象,8 秒后改变状态)
new ES6Promise((resolve, reject) => {
console.log('init.');
setTimeout(() => {
resolve(0); //状态Pending->Fulfilled
}, 8000);
}).then(
(value) => {
//由于构造函数内状态为Pending,此回调函数存入订阅队列
console.log('接收到的值为:', value);
return value + 1;
},
(error) => {
return new Error('msg');
}
);
// init.
// 接收到的值为:0
执行过程解析:
- 1.创建对象,输出
init. - 2.调用
then方法- 2.1
then源码内创建新(子)promise对象 - 2.2 判断当前状态为
Pending,调用subscribe函数把回调函数存入订阅队列,等待状态发生变化触发发布publish函数
- 2.1
- 3.
setTimeout触发resolve函数- 3.1
resolve源码内改变状态,触发发布publish函数
- 3.1
- 4.
then方法返回新(子)promise对象
示例三 演示订阅队列的过程(订阅队列存在多个回调函数的情况)
new ES6Promise((resolve, reject) => {
console.log('init.');
setTimeout(() => {
resolve(0); //状态Pending->Fulfilled
}, 8000);
})
.then(
(value) => {
//由于构造函数内状态为Pending,此回调函数存入订阅队列
console.log('接收到的值为:', value);
return value + 1;
},
(error) => {
return new Error('msg');
}
)
.then(
(value) => {
//由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
console.log('接收到的值为:', value);
return value + 1;
},
(error) => {
return new Error('msg');
}
);
// init.
// 接收到的值为:0
// 接收到的值为:1
执行过程解析:
- 同示例二,唯一不同的是订阅队列存在多个回调函数
图示:
这种链式调用,可以理解为当前.then()方法是由上一个.then()方法返回的新(子)promise对象调的)
示例四 演示立刻执行队列、订阅队列的过程
new ES6Promise((resolve, reject) => {
console.log('init.');
resolve(0); //状态Pending->Fulfilled
})
.then(
(value) => {
//由于构造函数内状态为Fulfilled,此回调函数存入立刻执行队列
console.log('接收到的值为:', value);
return value + 1;
},
(error) => {
return new Error('msg');
}
)
.then(
(value) => {
//由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
console.log('接收到的值为:', value);
return value + 1;
},
(error) => {
return new Error('msg');
}
);
// init.
// 接收到的值为:0
// 接收到的值为:1
执行过程解析:
- 1.创建对象,输出
init. - 2.立刻调用
resolve方法- 2.1
resolve源码内改变状态
- 2.1
- 3.调用第一个
then方法- 3.1
then源码内创建新(子)promise对象 - 3.2 判断当前状态为
Fulfilled,立刻调用asap方法将回调方法存入队列,等待立刻执行. - 3.3
then方法返回新(子)promise对象
- 3.1
- 4.调用第二个
then方法(被 3.3 返回的新(子)promise对象调用)- 4.1
then源码内创建新(子)promise对象 - 4.2 判断(3.3 返回的新(子)
promise对象)当前状态为Pending,调用subscribe函数把回调函数存入订阅队列,等待状态发生变化触发发布publish函数 - 4.3 返回新(子)
promise对象
- 4.1
图示:
示例五 演示多个立刻执行队列、订阅队列的过程
new ES6Promise((resolve, reject) => {
console.log('init.1');
resolve(); //状态Pending->Fulfilled
})
.then(() => {
//由于构造函数内状态为Fulfilled,此回调函数存入立刻执行队列
console.log('then1.1');
new ES6Promise((resolve, reject) => {
console.log('init.2');
setTimeout(() => {
resolve(); //状态Pending->Fulfilled
}, 8000);
})
.then(() => {
//由于构造函数内状态为Pending,此回调函数存入订阅队列
console.log('then2.1');
})
.then(() => {
//由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
console.log('then2.2');
});
})
.then(() => {
//由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
console.log('then1.2');
});
new ES6Promise((resolve, reject) => {
console.log('init.3');
resolve(); //状态Pending->Fulfilled
})
.then(() => {
//由于构造函数内状态为Fulfilled,此回调函数存入立刻执行队列
console.log('then3.1');
})
.then(() => {
//由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
console.log('then3.2');
});
//init.1
//init.3
//then1.1
//init.2
//then3.1
//then1.2
//then3.2
//then2.1
//then2.2
执行过程解析:
-
1.创建对象,输出
init.1 -
2.立刻调用
resolve方法- 2.1
resolve源码内改变状态
- 2.1
-
3.调用
then1.1- 3.1
then源码内创建新(子)promise对象 - 3.2 判断当前状态为
Fulfilled,立刻调用asap方法将回调方法存入队列,等待立刻执行. - 3.3
then方法返回新(子)promise对象
- 3.1
-
4.调用
then1.2- 4.1
then源码内创建新(子)promise对象 - 4.2 判断当前状态为
Pending,调用subscribe函数把回调函数存入订阅队列,等待状态发生变化触发发布publish函数 - 4.3 返回新(子)
promise对象
- 4.1
-
5.创建对象,输出
init.3 -
6.立刻调用
resolve方法- 6.1
resolve源码内改变状态
- 6.1
-
7.调用
then3.1- 7.1
then源码内创建新(子)promise对象 - 7.2 判断当前状态为
Fulfilled,立刻调用asap方法将回调方法存入队列,等待立刻执行. - 7.3
then方法返回新(子)promise对象
- 7.1
-
8.调用
then3.2- 8.1
then源码内创建新(子)promise对象 - 8.2 判断当前状态为
Pending,调用subscribe函数把回调函数存入订阅队列,等待状态发生变
- 8.1
至此代码自上而下执行同步函数、异步(回调)函数放入队列的过程已经梳理完,剩下便是执行回调函数时看是否有新的对象加入队列.
- 9.
then1.1回调函数执行- 9.1 创建对象,输出
init.2
- 9.1 创建对象,输出
- 10.调用
then2.1- 10.1
then源码内创建新(子)promise对象 - 10.2 判断当前状态为
Pending,调用subscribe函数把回调函数存入订阅队列,等待状态发生变化触发发布publish函数 - 10.3 返回新(子)
promise对象
- 10.1
- 11.调用
then.2.2- 11.1
then源码内创建新(子)promise对象 - 11.2 判断当前状态为
Pending,调用subscribe函数把回调函数存入订阅队列,等待状态发生变化触发发布publish函数 - 11.3 返回新(子)
promise对象
- 11.1
上面的执行过程解析确实比较复杂,所以我们直接看哪些存入立刻执行队列,哪些存入订阅队列就可以了.
图示:
按照图示最终输出结果:
init.1->init.3->then1.1->init.2->then3.1->then1.2->then3.2->then2.1->then2.2
示例六 演示返回值是
promise对象(看是否会改变执行顺序)
// new ES6Promise((resolve, reject) => {
// console.log('init.1');
// resolve(); //状态Pending->Fulfilled
// })
// .then(() => {
// //由于构造函数内状态为Fulfilled,此回调函数存入立刻执行队列
// console.log('then1.1');
// new ES6Promise((resolve, reject) => {
// console.log('init.2');
// setTimeout(() => {
// resolve(); //状态Pending->Fulfilled
// }, 8000);
// })
// .then(() => {
// //由于构造函数内状态为Pending,此回调函数存入订阅队列
// console.log('then2.1');
// })
// .then(() => {
// //由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
// console.log('then2.2');
// });
// })
// .then(() => {
// //由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
// console.log('then1.2');
// });
// //init.1
// //then1.1
// //init.2
// //then1.2
// //then2.1
// //then2.2
new ES6Promise((resolve, reject) => {
console.log('init.1');
resolve(); //状态Pending->Fulfilled
})
.then(() => {
//由于构造函数内状态为Fulfilled,此回调函数存入立刻执行队列
console.log('then1.1');
return new ES6Promise((resolve, reject) => {
console.log('init.2');
setTimeout(() => {
resolve(); //状态Pending->Fulfilled
}, 8000);
})
.then(() => {
//由于构造函数内状态为Pending,此回调函数存入订阅队列
console.log('then2.1');
})
.then(() => {
//由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
console.log('then2.2');
});
})
.then(() => {
//由于上一个then返回的新(子)promise对象的状态是Pending,所以此回调函数存入订阅队列
console.log('then1.2');
});
//init.1
//then1.1
//init.2
//then2.1
//then2.2
//then1.2
示例六注释代码为没有return返回promise对象的情况可以看一下输出结果.(此处不做解析)
执行过程解析:
- 想必大家已经知道如何分析了(这里忽略过程解析),我们直接看图示
图示:
按照之前的示例过程解析上面的图示应该是没错的输入结果应该是init.1->then1.1->init.2->then1.2->then2.1->then2.2
但是我们测试之后发现正确的结果却是这样的:init.1->then1.1->init.2->then2.1->then2.2->then1.2是什么原因导致出现这种问题?
既然多了一个return返回promise对象,那我们就从返回值开始
在then方法的回调函数里可以返回任何值(基本数据类型、引用(复合)数据类型),那源码内部是如何区别处理呢?
还是对源码进行一步一步分析吧,没有其它捷径...
//源码
function resolve(promise, value) {
if (promise === value) {
reject(promise, selfFulfillment());
} else if (objectOrFunction(value)) {
var then$$1 = void 0;
try {
then$$1 = value.then;
} catch (error) {
reject(promise, error);
return;
}
handleMaybeThenable(promise, value, then$$1);
} else {
fulfill(promise, value);
}
}
function selfFulfillment() {
return new TypeError('You cannot resolve a promise with itself');
}
resolve函数对value返回值做了什么.
if(promise === value)如果返回值和当前promise对象一样则调用selfFulfillment函数抛错(不可以返回对象本身)else if(objectOrFunction(value))如果返回值是object或function类型则需要做如下处理then$$1 = value.then;由于上面已经判断value是对象或函数,所以此处默认它具有then属性或方法- 调用
handleMaybeThenable函数做进一步处理
else返回值是基本数据类型则直接调用fulfill函数改变状态
//源码
function handleMaybeThenable(promise, maybeThenable, then$$1) {
if (
maybeThenable.constructor === promise.constructor &&
then$$1 === then &&
maybeThenable.constructor.resolve === resolve$1
) {
handleOwnThenable(promise, maybeThenable);
} else {
if (then$$1 === undefined) {
fulfill(promise, maybeThenable);
} else if (isFunction(then$$1)) {
handleForeignThenable(promise, maybeThenable, then$$1);
} else {
fulfill(promise, maybeThenable);
}
}
}
handleMaybeThenable函数又做了什么.
关于Thenable可以理解为类似promise一样含有then方法的对象
-
if多个判断验证参数maybeThenable是否为promise对象- 调用
handleOwnThenable函数,按照
- 调用
-
else非promise对象-
if (then$$1 === undefined)没有then属性或方法,直接调用fulfill函数改变状态 -
else if (isFunction(then$$1))确定是function类型(如function Person(){},Person.then=function(){};参数maybeThenable=Personthen$$1=Person.then)- 调用
handleForeignThenable函数处理带有自定义then方法的情况
- 调用
-
else,含有then属性的引用(复合)数据类型(如{name:"zhagnsan",then:'-'}),直接调用fulfill函数改变状态
-
function Pro() {}
Pro.then = function (resolve, reject) {
//...
};
new ES6Promise((resolve, reject) => {
console.log('init.1');
resolve(0);
})
.then((v) => {
console.log('then1.1');
return new Pro();
})
.then((v) => {
console.log('then1.2', v);
});
以上示例会把返回值当作没有then方法做处理.
因为Pro虽然有静态方法then但返回值是通过new创建的一个Pro对象,而对象上并没有then方法,所以源码resolve函数内then$$1 = value.then为undefined.
//源码
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,
function (value) {
return resolve(promise, value);
},
function (reason) {
return reject(promise, reason);
}
);
}
}
handleOwnThenable函数如何处理返回值为promise对象
首先handleOwnThenable函数按照返回值promise对象的状态进行处理
- 参数
thenable接收的是返回值promise对象 if、else处理状态为FulfilledRejected的情况,说明返回值promise对象的一系列链式then方法调用已完成,所以调用fulfillreject函数,继续执行之前的promise对象的队列并且 把then的返回值thenable._result传递给之前promise对象else处理状态为Pending的情况,如果返回值promise对象的状态未发生改变,则先存到订阅队列(注意这里的两个匿名函数是为改变之前的promise对象的状态准备的).然后等待返回值promise对象的状态改变调用一系列then方法
所以示例六的正确图示如下:
重要的事情说三遍
只要返回值是promise对象,则调用handleOwnThenable函数,之前的队列会在返回值promise对象的then方法之后执行
只要返回值是promise对象,则调用handleOwnThenable函数,之前的队列会在返回值promise对象的then方法之后执行
只要返回值是promise对象,则调用handleOwnThenable函数,之前的队列会在返回值promise对象的then方法之后执行
//源码
function handleForeignThenable(promise, thenable, then$$1) {
asap(function (promise) {
var sealed = false;
var error = tryThen(
then$$1,
thenable,
function (value) {
if (sealed) {
return;
}
sealed = true;
if (thenable !== value) {
resolve(promise, value);
} else {
fulfill(promise, value);
}
},
function (reason) {
if (sealed) {
return;
}
sealed = true;
reject(promise, reason);
},
'Settle: ' + (promise._label || ' unknown promise')
);
if (!sealed && error) {
sealed = true;
reject(promise, error);
}
}, promise);
}
function tryThen(then$$1, value, fulfillmentHandler, rejectionHandler) {
try {
then$$1.call(value, fulfillmentHandler, rejectionHandler);
} catch (e) {
return e;
}
}
handleForeignThenable函数如何处理自定义then方法
- 首先看到
asap函数应该立刻明白源码把这种thenable的情况作为像promise的then方法一样进行处理了.
重要的事情说三遍
对于这种thenable源码会作为像then一样处理
对于这种thenable源码会作为像then一样处理
对于这种thenable源码会作为像then一样处理
问答
为什么 then 要返回新的
promsie对象?
首先我们看以下两个示例代码
//示例一
var p1 = new ES6Promise((resolve, reject) => {
console.log('init.1');
resolve(0); //状态Pending->Fulfilled
});
p1.then((v) => {
console.log('then1.1', v);
return v + 1;
});
p1.then((v) => {
console.log('then1.2', v);
return v + 1;
});
p1.then((v) => {
console.log('then1.3', v);
return v + 1;
});
//init.1
//init.1 0
//init.1 0
//init.3 0
//示例二
var p1 = new ES6Promise((resolve, reject) => {
console.log('init.1');
resolve(0); //状态Pending->Fulfilled
});
var p2 = p1.then((v) => {
console.log('then1.1', v);
return v + 1;
});
console.log(p2);
var p3 = p2.then((v) => {
console.log('then1.2', v);
return v + 1;
});
var p4 = p3.then((v) => {
console.log('then1.3', v);
return v + 1;
});
//init.1
//init.1 0
//init.1 1
//init.3 2
我们知道之所以可以链式调用是因为方法内返回的是一个对象,使用对象调用方法形成链式调用.
如上面示例一通过对象调用then方法这种写法是没问题的,但你会发现输出结果和预期的不符合,问题出在哪里?
promise是基于状态变化进行操作的,所以当示例一中resolve(0);状态发生变化之后,所有p1.then方法几乎同时调用(因为都是使用的同一个p1对象).
我们再看示例二每次调用then则是通过返回的新对象,并且传递的值都是上一个then的返回值.
根据以上两个示例我们似乎知道了为什么要返回新的 promsie对象
promsie是基于状态变化操作的,每次返回新对象都是初始化状态,把控制权交给开发者(按需异步改变状态触发操作)确保了执行顺序.
如果一直是 pending 状态,
then里的onFulfillmentonRejection函数是否会调用
如果状态初始化之后未发生变化,那么无论有多少个链式then方法都不会被触发调用.
怎么知道源码
then方法内if(_state)是对FulfilledRejected状态做处理,else是对Pending状态做处理
- 创建对象(初始化)时的等待状态
Pending.(es6-promise 源码内 undefined 表示Pending) - 成功(解决)时的状态
Fulfilled.(es6-promise 源码内 1 表示Fulfilled) - 失败(拒绝)时的状态
Rejected.(es6-promise 源码内 2 表示Rejected)
其实在状态变化一节中已经提到过,源码内使用1``2作为Fulfilled Rejected的状态码 所以只有当状态是1 2的时候才会进入if语句.
为什么遇到返回值是
promise的时候要先执行?
new ES6Promise((resolve, reject) => {
console.log('init.1');
resolve();
})
.then(() => {
console.log('then1.1');
return new ES6Promise((resolve, reject) => {
console.log('init.2');
resolve(0);
})
.then((v) => {
console.log('then2.1', v);
return v + 1;
})
.then((v) => {
console.log('then2.2', v);
return v + 1;
});
})
.then((v) => {
console.log('then1.2', v);
});
//init.1
//then1.1
//init.2
//then2.1 0
//then2.2 1
//then1.2 2
这个问题上面示例六已经已经从源码分析的角度知道了答案,这次我们结合上面的示例(和示例六类似)从结果倒推,因为我们知道then方法是异步执行的(什么时候执行取决于状态的变化)所以遇到返回值是promise对象的情况(如上面示例then1.1函数返回值是promise对象),返回对象的链式调用then方法还没执行完毕,就开始调用then1.2的函数那方法怎么能接收到返回值呢(如上面示例 then2.2函数如果在then1.2回调函数之后执行,那么then1.2永远接收不到then2.2函数的返回值).
所以简单的理解先执行返回对象的链式then方法,就是为了确保其它then可以获取到正确的返回值
关于then一节啰嗦的比较多所以还需要品.细品.细细品.
all
构造函数做了什么
var promises = [
new ES6Promise((resolve, reject) => {
resolve(1);
}),
new ES6Promise((resolve, reject) => {
resolve(2);
}),
];
ES6Promise.all(promises).then((value) => {
console.log(value);
});
//[1,2]
我们先从上面的示例猜测一下all大概会做哪些操作.
all方法的参数是一个promise数组,最基本的应该对参数判断promise.all().then()通过all方法可以链式调用then方法,说明all方法返回的是一个promise对象,我们暂且称为父promise对象.then(value)参数value返回的是一组有序的值,说明内部应该会顺序记录每个promsie对象的状态和返回值- 调用
then方法的前提条件应该是所有的promise对象状态改变之后,触发条件改变父promsie对象的状态
带着这些猜测开始我们的源码解析.
//源码
function all(entries) {
return new Enumerator(this, entries).promise;
}
function Enumerator(Constructor, input) {
this._instanceConstructor = Constructor;
this.promise = new Constructor(noop);
if (!this.promise[PROMISE_ID]) {
makePromise(this.promise);
}
if (isArray(input)) {
this.length = input.length;
this._remaining = input.length;
this._result = new Array(this.length);
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());
}
}
首先all方法通过new创建对象的形式返回一个promise对象这也印证了上面我们说的第 2 点,接下来我们直接看Enumerator构造函数内是做了哪些操作.
从参数开始吧.
Constructor参数(类型 Function),从参数名知道它接收一个构造函数,这个构造函数是谁呢?我们再看all方法传递是this,这个this又指谁,那我们再看示例是如何调用all方法的.ES6Promise.all(promises),现在知道this指的是这个ES6Promise构造函数了input参数(类型 Array),一组promsie对象
this._instanceConstructor = Constructor;this.promise = new Constructor(noop);通过构造函数new创建对象,还记得all方法内return new Enumerator(this, entries).promise
返回的promise吗?没错就是这个this.promise,这样外部就可以通过这个promsie对象链式调用then等方法了.
if (isArray(input))else还是对参数下手了...印证了上面我们说的第 1 点,首先看else内对非数组做了什么处理?调用reject函数改变当前promsie的状态为Rejected到此就结束了.还是看if吧
感觉需要罗列一下
this.length记录promise数组长度this._remaining这个划重点,记录还剩下多少个promise对象未发生变化(简单理解有多少个promise未执行)默认都没执行所以this._remaining=input.lengththis._result记录每个promsie对象最终的返回值.
通过这些参数也印证了上面我们说的第 3 点,还是继续看如何处理每个promise对象的.
if (this.length === 0)数组为空的话调用fulfill函数改变当前promsie的状态为Fulfilled到此就结束了.
else我们先看一下this._remaining === 0这说明每个promise对象的状态都已经改变为Fulfilled所以也是调用fulfill函数改变当前promsie的状态为Fulfilled到此就结束了.(像上面的示例便是这种情况,通过new创建各个promise对象时在构造函数的回调内立刻改变状态resolve()).
通过对构造函数的解析是不是得出的结论就是上面的前 3 点内容.
顺序执行
我们还是重点看一下_enumerate方法做了什么,毕竟是从这里真正开始的.
//源码
Enumerator.prototype._enumerate = function _enumerate(input) {
for (var i = 0; this._state === PENDING && i < input.length; i++) {
this._eachEntry(input[i], i);
}
};
_enumerate方法循环迭代的每个promsie对象,没搞明白this._state === PENDING这个判断存在的作用(首先这个this._state不是指this.promsie._state,也没看到其它地方对这个this._state有操作)所以暂且忽略它,看_eachEntry方法如何处理每个promise对象.
//源码
Enumerator.prototype._eachEntry = function _eachEntry(entry, i) {
var c = this._instanceConstructor;
var resolve$$1 = c.resolve;
if (resolve$$1 === resolve$1) {
var _then = void 0;
var error = void 0;
var didError = false;
try {
_then = entry.then;
} catch (e) {
didError = true;
error = e;
}
if (_then === then && entry._state !== PENDING) {
this._settledAt(entry._state, i, entry._result);
} else if (typeof _then !== 'function') {
this._remaining--;
this._result[i] = entry;
} else if (c === Promise$1) {
var promise = new c(noop);
if (didError) {
reject(promise, error);
} else {
handleMaybeThenable(promise, entry, _then);
}
this._willSettleAt(promise, i);
} else {
this._willSettleAt(
new c(function (resolve$$1) {
return resolve$$1(entry);
}),
is
);
}
} else {
this._willSettleAt(resolve$$1(entry), i);
}
};
首先是通过_instanceConstructor获取resolve静态方法(如果不知道this._instanceConstructor是什么请从本节开头再看几次).
if (resolve$$1 === resolve$1)这里为什么要先判断参数的resolve静态方法与源码的resolve$1函数是否相等,还记得then一节提到过的Thenable吗?如果new Enumerator(Constructor,input)创建对象时传递的参数Constructor是类似Promise函数呢?所以对于这种情况直接进入else做处理,那if就可以专注处理状态变化的事情.
对于这么多if...else if...else判断还是顺序来看一下.
// 源码
if (_then === then && entry._state !== PENDING) {
this._settledAt(entry._state, i, entry._result);
}
对于if判断还是比较好理解的,判断循环迭代的每个promise对象的then方法与源码中的then函数相等并且迭代的promise对象的状态已经发生了变化(不再是初始化的Pending状态),想想本节开头的示例是不是就是这种情况.所以直接调用_settledAt方法处理状态变化之后的事情,既然已经提到_settledAt方法,我们看这方法做了哪些后面不再单独介绍_settledAt方法.
简单一句话if处理状态为Fulfilled或Rejected的操作
// 源码
Enumerator.prototype._settledAt = function _settledAt(state, i, value) {
var promise = this.promise;
if (promise._state === PENDING) {
this._remaining--;
if (state === REJECTED) {
reject(promise, value);
} else {
this._result[i] = value;
}
}
if (this._remaining === 0) {
fulfill(promise, this._result);
}
};
从参数开始看起
- state 循环迭代的
promise对象的状态 - i 循环迭代的
promise对象在数组中的索引位置 - value 循环迭代的
promise对象传递的值
if (promise._state === PENDING)只有在父promse的状态为Pending的情况才可以执行其它操作,从而再一次证明之前在状态变化一节提到的
状态只会是从 Pending->Fulfilled或Pending->Rejected改变一次且不可逆.
this._remaining--对剩余未执行的promise对象数量递减-1,接下来的if (state === REJECTED)是重点,这里判断是REJECTED状态的话要立刻调用reject(promise, value)函数为什么要这样操作?
举个例子:
长跑小组赛,A 组[A1(promise1),A2(promise2),A3(promise3)]三个队员在长跑过程中,组员 A2(promise2)由于发生意外(脚崴了)告诉评委(父 promise),评委立刻把 A 组成绩取消了(reject(promise, value)),那 A 组无论其他队员 A1(promise1)、A3(promise3)现在是什么状态都不重要了.
所以现在明白为什么要这样操作了吧(当迭代的某个promise对象的状态为REJECTED时要立刻调用reject(promise, value)).
把上面的例子转换为示例代码如下
//示例一
var promises = [
new ES6Promise((resolve, reject) => {
resolve('组员A1一切正常');
}),
new ES6Promise((resolve, reject) => {
reject(new Error('组员A2脚崴了'));
}),
new ES6Promise((resolve, reject) => {
resolve('组员A3一切正常');
}),
];
ES6Promise.all(promises).then(
(data) => {
console.log(data);
},
(error) => {
console.log(error);
}
);
// 示例二
var promises = [
new ES6Promise((resolve, reject) => {
resolve('组员A1一切正常');
}),
new ES6Promise((resolve, reject) => {
resolve('组员A2一切正常');
}),
new ES6Promise((resolve, reject) => {
reject(new Error('组员A3脚崴了'));
}),
];
ES6Promise.all(promises).then(
(data) => {
console.log(data);
},
(error) => {
console.log(error);
}
);
// 示例三
var promises = [
new ES6Promise((resolve, reject) => {
resolve('组员A1一切正常');
}),
new ES6Promise((resolve, reject) => {
resolve('组员A2一切正常');
}),
new ES6Promise((resolve, reject) => {
resolve('组员A3一切正常');
}),
];
ES6Promise.all(promises).then(
(data) => {
console.log(data);
},
(error) => {
console.log(error);
}
);
上面的示例一、示例二无论promsie对象在数组什么位置,只要有一个状态变为Rejected(调用reject函数),则调用then(onFulfillment, onRejection)方法的onRejection回调函数,所以只有当数组中所有的promise对象状态变为Fulfilled才会调用onFulfillment回调函数接收到返回值(如示例三).
相对于if那else就简单多了this._result[i] = value在结果集中对应的索引位置存入值.
this._remaining === 0判断是否还有未执行promse对象,如果全部执行完毕则调用fulfill(promise, this._result)函数改变父promise的状态.
有没有感觉讲到这里已经验证了上面提到的第 4 点.
讲完了if还是继续看else if (typeof _then !== 'function')对于有这种entry.then有then但检测类型不是function的情况做处理也比较简单this._remaining--对剩余未执行的promise对象数量递减-1 并且this._result[i] = entry在结果集中对应的索引位置存入值(这个值就是对象本身).
else if (c === Promise$1)这个判断的存在又有什么意义呢?
前面的if (_then === then && entry._state !== PENDING) else if (typeof _then !== 'function')都是针对循环迭代的promise对象做操作,(c的值是通过new Enumerator(Constructor,input)创建对象时外部传入的所以不确定c是否与源码内的Promise$1是同一个函数)而这里一旦验证c与源码内的Promise$1为同一个函数,可以直接使用handleMaybeThenable函数方便做剩下的操作.
else直接调用resolve$$1方法创建一个状态为Fulfilled的promise对象,调用_willSettleAt方法存入订阅队列.
最后我们补充一点:
上面看到在多个判断条件内调用了_willSettleAt方法,为什么这个方法会引起重视呢,又或者知道循环迭代promise对象状态是Fulfilled或Rejected的话会调用_settledAt方法做处理(包含通知父promsie是否需要更改状态).(上面对该方法已做过解析),刚才在else if (c === Promise$1)也讲过Pending状态的处理,但处理之后怎么通知父promise呢?所以我们还是带着问题解析_willSettleAt方法看是否能得到答案.
// 源码
Enumerator.prototype._willSettleAt = function _willSettleAt(promise, i) {
var enumerator = this;
subscribe(
promise,
undefined,
function (value) {
return enumerator._settledAt(FULFILLED, i, value);
},
function (reason) {
return enumerator._settledAt(REJECTED, i, reason);
}
);
};
还是从参数开始
- promise 这个参数不是指循环迭代的
promise对象,而是指存入订阅队列专门用来通知父promise做操作的promise对象,简单理解是专门负责 循环迭代的promise对象与父promsie对象之前通信的一个桥梁promsie - i 循环迭代的
promise对象索引值
调用subscribe函数把这个桥梁promsie存入订阅队列,当循环迭代的promise对象状态发生变化时,根据状态Fulfilled或Rejected调用桥梁promise对应的回调函数function (value){..};或function (reason) {...}把当前迭代的promise对象的状态、索引、返回值通过enumerator._settledAt方法传递给父promise做处理.
以上是对all静态方法内部执行过程的解析啰嗦的内容稍为多一点总结一下:
- 验证上面的 4 点内容
- 循环迭代的
promise对象状态为Rejected情况的处理(长跑小组赛示例) - 循环迭代的
promise对象状态为Pending的情况如何与父promsie的通信
其它
resolve
除了通过new创建对象,还提供了静态方法resolve可以快捷创建一个状态为Fulfilled的promise对象.(或者理解为语法糖)
//常规
new ES6Promise((resolve, reject) => {
resolve(1);
}).then((value) => {
console.log(value);
});
// 1
// 快捷方式(语法糖)
ES6Promise.resolve(1).then((value) => {
console.log(value);
});
//1
以上示例演示了通过new创建对象与快捷方式的区别,无论哪种方式最后都是返回promise对象调用then方法,所以我们看静态方法resolve的源码内部内部是否创建并返回了promise对象.
//源码
Promise$1.resolve = resolve$1;
function resolve$1(object) {
/*jshint validthis:true */
var Constructor = this;
if (
object &&
typeof object === 'object' &&
object.constructor === Constructor
) {
return object;
}
var promise = new Constructor(noop);
resolve(promise, object);
return promise;
}
静态方法内做的事情相对还是比较简单的.
- 判断
object参数类型,如果参数是一个promise对象不做任何处理直接返回该对象
var p1 = new ES6Promise((resolve, reject) => {
resolve(1);
});
var p2 = ES6Promise.resolve(p1).then((v) => {
console.log(v);
});
上面的示例演示就是这种情况p2通过快捷方式ES6Promise.resolve(p1)创建对象时传递的参数为p1是promise对象,所以构造函数内会直接返回该对象p1.
- 通过
new创建对象,并且内部调用resolve函数改变状态为Fulfilled - 最后返回内部创建的
promise对象
所以静态方法内部是帮我们创建了一个状态为Fulfilled的promise对象
reject
静态方法reject和resolve原理是一样的,所以可以直接参考上面的resolve方法.
//源码
Promise$1.reject = reject$1;
function reject$1(reason) {
/*jshint validthis:true */
var Constructor = this;
var promise = new Constructor(noop);
reject(promise, reason);
return promise;
}
catch
//源码
Promise.prototype.catch = function _catch(onRejection) {
return this.then(null, onRejection);
};
我们看到catch方法只接收一个参数onRejection,但需要注意的是内部调用then(null, onRejection)方法时第一个参数是null说明只有在promise对象的状态是Rejected情况下可以使用这种别名方式.
// 示例一 常规
new ES6Promise((resolve, reject) => {
reject(new Error('error'));
}).then(
(v) => {
console.log(v);
},
(e) => {
console.log(e);
}
);
// 示例二 别名方式
new ES6Promise((resolve, reject) => {
reject(new Error('error'));
}).catch((e) => {
console.log(e);
});
上面示例二直接使用catch方法,前提条件是已经知道promise对象的状态是Rejected,但我们在实际的开发中不知道状态的情况下该如何使用.
// 示例三
new ES6Promise((resolve, reject) => {
//使用setTimeout模拟请求
setTimeout(() => {
var s = new Date().getSeconds();
s % 2 === 0 ? resolve({ name: 'zhangsan' }) : reject(new Error('无响应'));
}, 5000);
}).then(
(v) => {
console.log(v);
},
(e) => {
console.log(e);
}
);
示例三通过判断时间秒数(0-59)的奇偶情况模拟请求有无数据.
- 偶数表示有数据,调用
resolve函数改变状态为Fulfilled - 奇数表示请求无响应,调用
reject函数改变状态为Rejected
对于像示例三这种不知道状态的情况,显然是不适合直接使用catch方法的,下面示例四、示例五演示了这种情况.
// 示例四
new ES6Promise((resolve, reject) => {
//使用setTimeout模拟请求
setTimeout(() => {
var s = new Date().getSeconds();
s % 2 === 0 ? resolve({ name: 'zhangsan' }) : reject(new Error('无响应'));
}, 5000);
}).then(null, (e) => {
console.log(e);
});
// 示例五 (示例四的别名写法)
new ES6Promise((resolve, reject) => {
//使用setTimeout模拟请求
setTimeout(() => {
var s = new Date().getSeconds();
s % 2 === 0 ? resolve({ name: 'zhangsan' }) : reject(new Error('无响应'));
}, 5000);
}).catch((e) => {
console.log(e);
});
可以通过示例四、示例五看出来 无论是then还是catch方法都是只处理了Rejected的状态,所以如果秒数返回偶数,调用resolve函数改变状态为Fulfilled则没有对应的回调函数做处理.
以上示例还是使用常规then的写法吧那catch适用什么场景呢我们接着看下面的示例.
// 示例六
new ES6Promise((resolve, reject) => {
//使用setTimeout模拟请求
setTimeout(() => {
resolve({ name: 'zhangsan' });
}, 5000);
}).then(
(v) => {
console.log('开始处理数据....');
throw new Error('数据处理过程发生错误...');
},
(e) => {
console.log(e);
}
);
示例六的场景想必大家也遇到过在对数据处理的过程中发生异常该如何处理,先回顾一下then(onFulfillment, onRejection)方法的使用方式.
接收 2 个参数
onFulfillment回调函数处理promise对象状态为Fulfilled的情况onRejection回调函数是处理promise对象状态为Rejected的情况
这两个回调函数是平级,也就是说由于promise对象只会变化一次状态,所以 2 个回调函数只对同一个promise对象负责.
简单理解onFulfillment回调函数里发生翻天覆地的事情,onRejection也不会多管闲事(因为promsie对象已经选择了onFulfillment)
那怎么办出了问题总要有背锅的吧.让新人背吧.
// 示例七
new ES6Promise((resolve, reject) => {
//使用setTimeout模拟请求
setTimeout(() => {
resolve({ name: 'zhangsan' });
}, 5000);
})
.then(
(v) => {
console.log('开始处理数据....');
throw new Error('数据处理过程发生错误...');
},
(e) => {
console.log(e);
}
)
.then(
(v) => {
console.log(v);
},
(e) => {
console.log('上一个then甩出来什么锅都是我的', e);
}
);
如示例七所示由于then是可以链式调用所以把异常甩给下一任(甩给下一个then).至此应该明白catch的使用场景了吧,如果示例七还没看明白那就看示例八
// 示例八
new ES6Promise((resolve, reject) => {
//使用setTimeout模拟请求
setTimeout(() => {
resolve({ name: 'zhangsan' });
}, 5000);
})
.then(
(v) => {
console.log('开始处理数据....');
throw new Error('数据处理过程发生错误...');
},
(e) => {
console.log(e);
}
)
.then(null, (e) => {
console.log('上一个then甩出来什么锅都是我的', e);
});
如果示例八还没看明白那就看示例九
// 示例九 (示例八的别名)
new ES6Promise((resolve, reject) => {
//使用setTimeout模拟请求
setTimeout(() => {
resolve({ name: 'zhangsan' });
}, 5000);
})
.then(
(v) => {
console.log('开始处理数据....');
throw new Error('数据处理过程发生错误...');
},
(e) => {
console.log(e);
}
)
.catch((e) => {
console.log('上一个then甩出来什么锅都是我的', e);
});
race
// 源码
function race(entries) {
/*jshint validthis:true */
var Constructor = this;
if (!isArray(entries)) {
return new Constructor(function (_, reject) {
return reject(new TypeError('You must pass an array to race.'));
});
} else {
return new Constructor(function (resolve, reject) {
var length = entries.length;
for (var i = 0; i < length; i++) {
Constructor.resolve(entries[i]).then(resolve, reject);
}
});
}
}
race静态方法与all静态方法使用方式一样,需要注意的是race静态方法内循环迭代的promise对象只要有一个状态发生变化Fulfilled或Rejected则会进行后续处理(调用父promise的then方法).
感兴趣的自己解析一下race源码吧.
总结
以上章节就是目前对 es6 promise 的理解,还望多多指教.