实施Promise.series作为Promise.all的替代品

我看到Promise.all的这个示例实现 – 它并行运行所有promise – 实现Promise.all

请注意,我正在寻找的function类似于Bluebird的Promise.mapSeries http://bluebirdjs.com/docs/api/mapseries.html

我正在尝试创buildPromise.series,我有这个似乎按预期工作( 它实际上是完全错误的,不使用它,看答案 ):

Promise.series = function series(promises){ return new Promise(function(resolve,reject){ const ret = Promise.resolve(null); const results = []; promises.forEach(function(p,i){ ret.then(function(){ return p.then(function(val){ results[i] = val; }); }); }); ret.then(function(){ resolve(results); }, function(e){ reject(e); }); }); } Promise.series([ new Promise(function(resolve){ resolve('a'); }), new Promise(function(resolve){ resolve('b'); }) ]).then(function(val){ console.log(val); }).catch(function(e){ console.error(e.stack); }); 

然而,这个实现的一个潜在的问题是,如果我拒绝承诺,它似乎没有抓住它:

  Promise.series([ new Promise(function(resolve, reject){ reject('a'); // << we reject here }), new Promise(function(resolve){ resolve('b'); }) ]).then(function(val){ console.log(val); }).catch(function(e){ console.error(e.stack); }); 

有没有人知道为什么错误不会被捕获,如果有一种方法来解决这个承诺?

根据评论,我做了这个改变:

 Promise.series = function series(promises){ return new Promise(function(resolve,reject){ const ret = Promise.resolve(null); const results = []; promises.forEach(function(p,i){ ret.then(function(){ return p.then(function(val){ results[i] = val; }, function(r){ console.log('rejected'); reject(r); // << we handle rejected promises here }); }); }); ret.then(function(){ resolve(results); }, function(e){ reject(e); }); }); } 

但是这仍然不能按预期工作…

thenforEach循环中返回的promise不会处理潜在的错误。

正如@slezica的评论所指出的那样,尽量使用reduce而不是forEach ,这将所有的承诺联系在一起。

 Promise.series = function series(promises) { const ret = Promise.resolve(null); const results = []; return promises.reduce(function(result, promise, index) { return result.then(function() { return promise.then(function(val) { results[index] = val; }); }); }, ret).then(function() { return results; }); } 

请记住,承诺已经在这个时候“运行”了。 如果你真的想要连续运行你的承诺,你应该调整你的函数,并传入一组返回promise的函数。 像这样的东西:

 Promise.series = function series(providers) { const ret = Promise.resolve(null); const results = []; return providers.reduce(function(result, provider, index) { return result.then(function() { return provider().then(function(val) { results[index] = val; }); }); }, ret).then(function() { return results; }); } 

这是承诺如何工作的常见误解。 人们希望有一个相当于并行Promise.all的顺序。

但承诺不“运行”的代码,他们只是一个附加完成callback的返回值 。

Promise.all所要做的Promise.all数组是一个返回值的数组。 没有办法按顺序“运行”它们,因为没有办法“运行”返回值。

Promise.all只是给你一个代表许多的承诺。

要按顺序运行,从一系列要运行的东西开始,即函数:

 let p = funcs.reduce((p, func) => p.then(() => func()), Promise.resolve()); 

或运行一个函数的值的数组:

 let p = values.reduce((p, val) => p.then(() => loadValue(val)), Promise.resolve()); 

在这里阅读减less。

更新:为什么承诺不“运行”的代码。

大多数人直观地理解callback并不是平行的。

(旁边的工作人员)JavaScript本质上是事件驱动和单线程的,并且永远不会并行运行。 只有浏览器function,例如fetch(url)才能真正做到并行工作,所以“asynchronous操作”是一个同步函数调用的委婉语,它立即返回,但被给予一个callback(例如,将被调用的地方) 稍后调用。

承诺不会改变这个现实。 它们不具有固有的asynchronousfunction(*),超出了callback的function。 从他们最基本的angular度来看,他们是一个非常巧妙的技巧,可以逆转你需要指定callback的顺序。

*) 从技术上说,promises确实有一些优于callback的function,这是大多数实现中的一个微任务队列,这只是意味着promise可以在JavaScript事件循环的当前曲柄的尾部进行调度。 但是,这仍然不是很大的不同,而是一个细节。

编辑2

根据你的编辑,你正在寻找蓝鸟提供的Promise.mapSeries 。 你已经给了我们一个移动目标,所以这个编辑改变了我以前的答案的方向,因为mapSeries函数的工作原理与按顺序执行一个Promises集合非常不同。

 // mock bluebird's mapSeries function // (Promise [a]) -> (a -> b) -> (Promise [b]) Promise.prototype.mapSeries = function mapSeries(f) { return this.then(reducek (ys=> x=> k=> { let value = f(x); let next = x=> k([...ys, x]); return value instanceof Promise ? value.then(next) : next(value); }) ([])); }; 

只是为了得到如何使用这个顶级的想法

 // given: (Promise [a]) and (a -> b) // return: (Promise [b]) somePromiseOfArray.mapSeries(x=> doSomething(x)); //=> somePromiseOfMappedArray 

这依赖于一个小型的reducek帮助器,除了callback接收到一个额外的延续参数之外,它像普通的reduce一样运行。 这里的主要优点是我们的减less过程可以select现在是asynchronous的。 只有在继续应用时才会进行计算。 这被定义为一个单独的,因为它是一个有用的程序, 在mapSeries有这个逻辑会使它过于复杂。

 // reduce continuation helper // (a -> b -> (a -> a)) -> a-> [b] -> a const reducek = f=> y=> ([x, ...xs])=> { if (x === undefined) return y; else return f (y) (x) (y => reducek (f) (y) (xs)); }; 

所以你可以对这个帮手的工作有一个基本的了解

 // normal reduce [1,2,3,4].reduce((x,y)=> x+y, 0); //=> 10 // reducek reducek (x=> y=> next=> next(x+y)) (0) ([1,2,3,4]); //=> 10 

接下来,我们将在演示中使用两个操作。 一个完全同步,一个返回一个Promise。 这表明mapSeries也可以处理mapSeries自身的迭代值。 这是蓝鸟定义的行为。

 // synchronous power // Number -> Number -> Number var power = x=> y=> Math.pow(y,x); // asynchronous power // Number -> Number -> (Promise Number) var powerp = x=> y=> new Promise((resolve, reject)=> setTimeout(() => { console.log("computing %d^%d...", y, x); if (x < 10) resolve(power(x)(y)); else reject(Error("%d is just too big, sorry!", x)); }, 1000)); 

最后,一个小帮手用来帮助login演示

 // log promise helper const logp = p=> p.then( x=> console.log("Done:", x), err=> console.log("Error:", err.message) ); 

演示时间! 在这里,我要dogfood我自己的mapSeries实现按顺序运行每个演示。

因为mapSeries除了要在Promise上调用,所以我用Promise.resolve(someArrayOfValues)

 // demos, map each demo to the log Promise.resolve([ // fully synchronous actions map/resolve immediately ()=> Promise.resolve([power(1), power(2), power(3)]).mapSeries(pow=> pow(2)), // asynchronous items will wait for resolve until mapping the next item ()=> Promise.resolve([powerp(1), powerp(2), powerp(3)]).mapSeries(pow=> pow(2)), // errors bubble up nicely ()=> Promise.resolve([powerp(8), powerp(9), powerp(10)]).mapSeries(pow=> pow(2)) ]) .mapSeries(demo=> logp(demo())); 

继续,现在运行演示

 // reduce continuation helper // (a -> b -> (a -> a)) -> a-> [b] -> a const reducek = f=> y=> ([x, ...xs])=> { if (x === undefined) return y; else return f (y) (x) (y => reducek (f) (y) (xs)); }; // mock bluebird's mapSeries function // (Promise [a]) -> (a -> b) -> (Promise [b]) Promise.prototype.mapSeries = function mapSeries(f) { return this.then(reducek (ys=> x=> k=> (x=> next=> x instanceof Promise ? x.then(next) : next(x) ) (f(x)) (x=> k([...ys, x])) ) ([])); }; // synchronous power // Number -> Number -> Number var power = x=> y=> Math.pow(y,x); // asynchronous power // Number -> Number -> (Promise Number) var powerp = x=> y=> new Promise((resolve, reject)=> setTimeout(() => { console.log("computing %d^%d...", y, x); if (x < 10) resolve(power(x)(y)); else reject(Error("%d is just too big, sorry!", x)); }, 1000)); // log promise helper const logp = p=> p.then( x=> console.log("Done:", x), err=> console.log("Error:", err.message) ); // demos, map each demo to the log Promise.resolve([ // fully synchronous actions map/resolve immediately ()=> Promise.resolve([power(1), power(2), power(3)]).mapSeries(pow=> pow(2)), // asynchronous items will wait for resolve until mapping the next item ()=> Promise.resolve([powerp(1), powerp(2), powerp(3)]).mapSeries(pow=> pow(2)), // errors bubble up nicely ()=> Promise.resolve([powerp(8), powerp(9), powerp(10)]).mapSeries(pow=> pow(2)) ]) .mapSeries(f=> logp(f())); 

@ forrert的答案是非常重要的

Array.prototype.reduce有点混乱,所以这里是一个没有reduce的版本。 请注意,为了实际运行promise系列,我们必须将每个承诺包装在一个提供者函数中,并只在Promise.series函数中调用提供者函数。 否则,如果承诺没有包含在函数中,承诺将立即开始运行,我们无法控制它们的执行顺序。

 Promise.series = function series(providers) { const results = []; var ret; providers.forEach(function(p, i){ if(ret){ ret = ret.then(function(){ return p().then(function(val){ results[i] = val; }); }); } else{ ret = p().then(function(val){ results[i] = val; }); } }); return ret.then(function(){ return results; }); } 

使用reduce的等效function:

 Promise.series = function series(providers) { const ret = Promise.resolve(null); const results = []; return providers.reduce(function(result, provider, index) { return result.then(function() { return provider().then(function(val) { results[index] = val; }); }); }, ret).then(function() { return results; }); } 

你可以用这个testing两个函数:

 Promise.series([ function(){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log('a is about to be resolved.') resolve('a'); },3000); }) }, function(){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log('b is about to be resolved.') resolve('b'); },1000); }) } ]).then(function(results){ console.log('results:',results); }).catch(function(e){ console.error('Rejection reason:', e.stack || e); }); 

请注意,附加函数或修改本地全局variables不是一个好主意,就像我们刚刚做的那样。 但是,也请注意,本地库的作者也离开了我们需要的function的本地库