关于promise.all详细原理
在我看来Promise.all只是一个语法糖,和并行没什么关系吧
function all(parr){
var maxL = parr.length;
return new Promise(function(ok,no){
var _value = [],pass=0;
var _then = function(i){
return function(data){
pass++;
_value[i] = data;
if(pass == maxL){
ok(_value);
}
}
}
if(parr.length <= 0) return ok([]);
for(var i=0; i<parr.length; i++){
Promise.resolve(parr[i]).then(_then(i),no)
}
})
}
@cnwhy thanks,感觉上promise.all并不是并行的,但是看到很多人都说是。而我虽然没看他的实现源码,但是感觉他是放在microtask queue里面去执行的。也就是说应该是串行。所以想看看有没了解它的原理的来解释下。
Promise.all 接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法。
function getURL(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open(‘GET’, URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } var request = { comment: function getComment() { return getURL(‘http://azu.github.io/promises-book/json/comment.json’).then(JSON.parse); }, people: function getPeople() { return getURL(‘http://azu.github.io/promises-book/json/people.json’).then(JSON.parse); } }; function main() { return Promise.all([request.comment(), request.people()]); } // 运行示例 main().then(function (value) { console.log(value); }).catch(function(error){ console.log(error); });
可以运行这个demo看看
@lpm0205 thank.但是我需要的是它原理,很多人说他是并行执行的,但是结果上并没有说明是并行执行的。
确实是并行的,翻了下源码大致流程应该是这样的(node v6.9.1):
function PromiseAll(iterable) {
var deferred = NewPromiseCapability(this);
var resolutions = new InternalArray();
function CreateResolveElementFunction(index, values, promiseCapability) {
var alreadyCalled = false;
return (x) => {
if (alreadyCalled === true) return;
alreadyCalled = true;
//把这个promise实例的执行结果缓存到resolutions数组中
values[index] = x;
};
}
var i = 0;
for (var value of iterable) {
//把传入的参数用promise.resolve转化为Promise对象实例
var nextPromise = this.resolve(value);
//转换完成后调用then方法执行
nextPromise.then(
CreateResolveElementFunction(i, resolutions, deferred),
deferred.reject);
++i;
}
return deferred.promise;
}
这里看起来就是把Promise.all传入的数组,用for of进行了遍历,然后对每一项先用promise.resolve(this.resolve)转化为Promise对象并且赋值给nextPromise,接着就调用nextPromise.then方法,把每一个promise对象的结果缓存到resolutions数组中:
values[index] = x
最后返回deferred.promise,当我们调用Promise.all([]).then的时候,得到的就是一个数组,里面的每一项元素对应传入的每一项Promise的处理结果。如果理解有误,欢迎指正
至于提到的并行,应该说绝大部分时候我们指的是异步操作的并行 实际上来说:
Promise.all([异步操作1,异步操作2,异步操作3]).then
虽然依旧是循环先执行异步操作1.then,再执行异步操作2.then,但是这个for of循环并不会等到异步操作.then执行完成后再去执行异步操作2.then,上面的代码很容易看到决定resolutions数组结果顺序的变量i是传入CreateResolveElementFunction函数的,因此这个for of循环体现出了这些异步操作的并行
@hyj1991 这个好像我也看过,但是这是promise,通常来讲整体代码是放到同一个macrotask 跟microtask里面的,而promise是会放到microtask queue里面执行,这样的话,其实它并不是并行,而是串行。因为丢到queue里面是先进先出的(如无其他特殊处理的话)。
@hyj1991 所以其实他并不是真正意义上的并行,比如说在内存上并行执行。我只是比较疑惑说他是并行但是确不是并行的一个结果。thanks.
- 田径赛跑,所有的人都跑完了,all就结束了。
- 田径赛跑,第一个跑到了,race就结束了。
@AberGLJ microtask会一次性把当前队列任务全部执行掉,和Promise.all的串行无关吧:
while (pending_microtask_count() > 0) {
int num_tasks = pending_microtask_count();
Handle<FixedArray> queue(heap()->microtask_queue(), this);
//清空microTask队列
set_pending_microtask_count(0);
heap()->set_microtask_queue(heap()->empty_fixed_array());
FOR_WITH_HANDLE_SCOPE(isolate, int, i = 0, i, i < num_tasks, i++, {
Handle<Object> microtask(queue->get(i), this);
//...处理代码
});
}
而且在Node中,microTask仅在每一次_tickCallback的调用中被执行:
- 把存储的nextTick函数的nextTickQueue中全部拿出来处理掉(可能nextTickQueue没有待处理函数)
- 然后才会执行_runMicrotasks操作;
而_tickCallback本身看起来目前只有三处会被调用:runMain之后(入口js文件解析完毕,进入event loop之前),以及io回调和timer回调。
所以我倾向于每一次的microtask执行的是上面源码中的PromiseAll函数本身,而不是把promise.all注册的promise实例分散注册到microtask_queue里面去
@i5ting thank. 我现在理解的promise.all应该是数组里面的promise其实都是放到queue里面的,所以它的并行意思指的for of循环体现出了这些异步操作的并行,并非真正的并行。
@AberGLJ for of控制的执行,必然不是真正的类似多线程实现的并行,这一点我觉得是毋庸置疑的
@hyj1991 所以你的意思是PromiseAll函数是整体放到了microtask里面去执行,而不是在macrotask 或者叫task里面执行的?然后他应该什么时候执行里面的promise实例呢?
@hyj1991 对,我也认为并非真正的类似多线程实现的并行。所以才对它感到困惑。
@AberGLJ 回来又翻了下代码,之前应该是想错了,Promise链的每一环的执行其实在new Promise的时候就执行掉了:
var GlobalPromise = function Promise(resolver) {
try {
//传入的(resolve, reject)=>{}处理函数
resolver(callbacks.resolve, callbacks.reject);
} catch (e) {
%_Call(callbacks.reject, UNDEFINED, e);
} finally {
%DebugPopPromise();
}
return promise;
};
所以实际上调用Promise.all时,传入的数组里面如果都是Promise实例的话,其实这里已经按照new Promise的顺序执行了,并且会记录下当前的状态(-1,0,+1)。
至于注册到microtaskqueue中执行的部分,并不是我之前以为的promise构造方法中的处理函数,而实际上是Promise的回调函数,即then方法里注册的onResolve和onReject(如果有的话)。
这样一来就比较明白了,new Promise中的操作不管是异步还是同步,都是按照在代码中new的顺序串行执行的;然后then方法注册的回调会分两种情况(准确说是三种,但是resolve和reject状态都会进microtask queue所以认为是一种):
- 如果当前的promise实例状态是0(Pending,比如异步获取数据未返回),那么把then方法传入的onResolve和onReject方法塞进promise实例初始化时的一个内建数组中(new InternalArray),名字对应是resolve方法塞入promiseOnResolveSymbol数组,reject方法塞入promiseOnRejectSymbol数组
- 如果当前的promise实例状态为1或者-1(resolve或者reject),那么直接会调用PromiseEnqueue注册一个回调函数到microtask queue,这个回调函数的作用就是执行对应的onResolve或者obReject方法
那么第一种的pending状态是调用then方法时该promise实例的状态,等我们写的异步方法执行结束后会转向对应的resolve或者reject状态,此时(既当我们resolve(data)或者reject(error)时),会调用真正的PromiseDone方法,该方法功能就是判断上面的promiseOnResolveSymbol数组或者promiseOnRejectSymbol数组中是否有待处理的任务,也就是上面第一种情况下会添加的then函数传入的方法,如果有,那么同样会调用PromiseEnqueue注册一个回调函数到microtask queue来处理回调
@AberGLJ 好好理解一下事件驱动吧
promise.all 本身并不是并行的; 并行都是,调用的那些回调函数,是异步属性;
它相当于等待所有事件回调 执行完成 再执行; 与EventProxy这个组件的 原理很相近。 都要继承自EventEmitter
@slclub ES6 v8内建的Promise和EventEmitter没有关系,PromiseAll函数是靠计数变量–count来控制结果的:
if (--count === 0) {
var valuesArray = [];
//内建数组赋值函数,最终传递给Promise.all().then的数据就是valuesArray
%MoveArrayContents(values, valuesArray);
//类似于f.call的函数调用方法,把结果传给deferred对象
%_Call(promiseCapability.resolve, UNDEFINED, valuesArray);
}
@hyj1991 恩恩,其实new promise的时候是在task队列中执行(整体的代码),所以在执行的时候就已经是按照在代码中new的顺序将promise注册到microtaskqueue里面,其实就是把then里面的函数放到了队列里去执行。所以本身promise.all并非真正的并行执行,实际还是根据注册顺序在队列里串行执行的。