实施Promise.series作为Promise.all的替代 [英] Implementing Promise.series as alternative to Promise.all
问题描述
我看到Promise.all的这个示例实现 - 并行运行所有的承诺 - 实现Promise.all
请注意,我正在寻找的功能类似于Bluebird的Promise.mapSeries http://bluebirdjs.com/docs/api/mapseries.html
我正在尝试创建Promise.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');
})
])然后(function(val){
console.log(val);
})。catch(function(e){
console.error(e.stack);
});
但是,这个实现的一个潜在问题是,如果我拒绝承诺,似乎不抓住它:
Promise.series([
new Promise(function(resolve,reject)){
拒绝('a'); //<我们拒绝这里
}),
new Promise(function(resolve){
resolve('b');
))
])然后(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 val){
results [i] = val;
},
function(r){
console.log('rejected');
reject(r) ; //<<我们处理被拒绝的承诺
});
});
});
ret.then(function() b $ b resolve(results);
},
function(e){
reject(e);
});
});
}
但这仍然无法按预期工作...
编辑2
对于编辑,您正在寻找蓝鸟提供的 Promise.mapSeries
。你给了我们一个移动的目标,所以这个编辑改变了我之前答案的方向,因为 mapSeries
函数的工作方式与在序列中执行Promises集合非常不同订单。
// mock bluebird的mapSeries函数
//(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]);
返回值instanceof Promise?value.then(next):next );
})([]));
};
只是为了获得如何使用这个方法的顶级想法
//给定:(Promise [a])和(a - > b)
// return:(Promise [b])
somePromiseOfArray.mapSeries(x => doSomething(x)); // => somePromiseOfMappedArray
这依赖于一个小的 reducek
帮助其操作类似于正常的 reduce
,除了回调接收附加的连续参数。这里的主要优点是我们的减少过程可以选择现在异步。计算仅在应用延续时才进行。这被定义为一个单独的,因为它是一个有用的程序,全部是自己的;在 mapSeries
里面有这个逻辑会使它过于复杂。
/ reduce continue 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
接下来,我们将在演示中使用两个操作。一个是完全同步的,一个返回一个承诺。这表明, mapSeries
也可以用作Promises本身的迭代值。这是蓝鸟定义的行为。
//同步电源
// Number - >数字 - > Number
var power = x => Y => Math.pow(Y,X);
//异步电源
//数字 - >数字 - > (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(错误(%d太大了,对不起!,x));
},1000));
最后,一个小帮手用来方便登录演示
// log promise helper
const logp = p =>
p.then(
x => console.log(Done:,x),
err => console.log(Error:,err.message)
);
演示时间!在这里,我要 dogfood 我自己执行 mapSeries
按顺序运行每个演示!
因为 mapSeries
除了被请求承诺,我用 Promise.resolve(someArrayOfValues)
//演示,将每个演示映射到日志
Promise.resolve([
//完全同步动作映射/立即解析
()=> promise.resolve([power(1),power(2),power(3)])。mapSeries(pow => pow(2)),
//异步项目将等待解析直到映射下一个项目
()=> Promise.resolve([powerp(1),powerp(2),powerp(3)])。mapSeries(pow => pow(2)),
//错误气泡很好
()=> Promise.resolve([powerp(8),powerp(9),powerp(10)])。mapSeries(pow => pow (2))
])
.mapSeries(demo => logp(demo()));
继续,现在运行演示
// reduce continue helper //(a - > b - >(a - > a)) - > A-> [b] - > aconst reducek = f => Y => ([x,... xs])=> {if(x === undefined)return y; else return f(y)(x)(y => reducek(f)(y)(xs));}; // mock bluebird的mapSeries函数//(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(x))(f(x))(x => k([... ys,x])))([]));}; //同步功率//数字 - >数字 - >数量幂= x => Y => Math.pow(y,x); //异步电源// Number - >数字 - > (承诺号)var powerp = x => Y =>新的Promise((resolve,reject)=> setTimeout(()=> {console.log(computing%d ^%d ...,y,x); if(x< 10) (x)(y)); else reject(Error(%d is too big,sorry!,x));},1000)); // log promise helperconst logp = p => p.then(x => console.log(Done:,x),err => console.log(Error:,err.message)); //演示,将每个演示映射到logPromise。 resolve([//完全同步动作map / resolve immediate()=> Promise.resolve([power(1),power(2),power(3)])mapSeries(pow => pow(2)) ,//异步项目将等待解析直到映射下一个项目()=> Promise.resolve([powerp(1),powerp(2),powerp(3)])。mapSeries(pow => pow )),//错误气泡很好()=> Promise.resolve([powerp(8),powerp(9),powerp(10)])。mapSeries(pow => pow(2))])。 mapSeries(f => logp(f()));
< >
编辑
我重新接受了这个问题,因为一系列承诺应该被视为一个承诺的链或组合。每个解决的承诺将会为下一个承诺带来价值。
对于@ Zhegan的言论,对于系列
函数采取一系列承诺创建者,否则无法保证承诺将连续运行。如果您通过一系列Promises,每个承诺将立即运行其执行人并开始工作。因此,承诺2的工作不可能依赖于承诺1的完成工作。
根据@ Bergi的话,我以前的答案有点怪异。我认为这个更新使事情更加一致。
Promise系列没有错误
//([(a->(Promise b)),(b->(Promise c)]) ..]) - > a - > (Promise c)Promise.series = function series(tasks){return x => tasks.reduce((a,b)=> a.then(b),Promise.resolve(x));}; // a - > [a] - > (Promise [a])var concatp = x => XS =>新的Promise((resolve,reject)=> setTimeout(()=> {console.log(xs,x); if(xs.length< 3)resolve(xs.concat([x])); else reject(Error('too many items'));},250)); var done =(x)=> console.log('done:',x); var err =(e)=> console.log('error:',e.message); Promise.series([concatp(3),concatp(6),concatp(9)])([]).then(done,err); // [ ] 3 // [3] 6 // [3,6] 9 // done:[3,6,9]
/ p>
具有错误的Promise系列
//([(a->(Promise b)),(b->(Promise c)]),...]) - > a - > (Promise c)Promise.series = function series(tasks){return x => tasks.reduce((a,b)=> a.then(b),Promise.resolve(x));}; // a - > [a] - > (Promise [a])var concatp = x => XS =>新的Promise((resolve,reject)=> setTimeout(()=> {console.log(xs,x); if(xs.length< 3)resolve(xs.concat([x])); else reject(Error('too many items'));},250)); var done =(x)=> console.log('done:',x); var err =(e)=> console.log('error:',e.message); Promise.series([concatp(3),concatp(6),concatp(9),concatp(12)])([]).then(done,err ); // [] 3 // [3] 6 // [3,6] 9 // [3,6,9] 12 //错误:太多的项目
I saw this example implementation of Promise.all - which runs all promises in parallel - Implementing Promise.all
Note that the functionality I am looking for is akin to Bluebird's Promise.mapSeries http://bluebirdjs.com/docs/api/mapseries.html
I am making an attempt at creating Promise.series, I have this which seems to work as intended (it actually is totally wrong, don't use it, see answers):
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);
});
However, one potential problem with this implementation is that if I reject a promise, it doesn't seem to catch it:
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);
});
does anyone know why the error doesn't get caught and if there is a way to fix this with Promises?
According to a comment, I made this change:
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);
});
});
}
but this still doesn't work as expected...
EDIT 2
According to your edit, you're looking for Promise.mapSeries
as provided by bluebird. You've given us a bit of a moving target, so this edit changes direction from my previous answer because the mapSeries
function works very differently than just executing a collection of Promises in serial order.
// 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);
}) ([]));
};
Just to get a top-level idea of how this would be used
// given: (Promise [a]) and (a -> b)
// return: (Promise [b])
somePromiseOfArray.mapSeries(x=> doSomething(x)); //=> somePromiseOfMappedArray
This relies on a small reducek
helper which operates like a normal reduce
except that the callback receives an additional continuation argument. The primary advantage here is that our reducing procedure has the option of being asynchronous now. The computation will only proceed when the continuation is applied. This is defined as a separately because it's a useful procedure all on its own; having this logic inside of mapSeries
would make it overly complicated.
// 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));
};
So you can get a basic understanding of how this helper works
// 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
Next we have two actions that we'll use in our demos. One that is completely synchronous and one that returns a Promise. This demonstrates that mapSeries
can also work on iterated values that are Promises themselves. This is the behaviour defined by bluebird.
// 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));
Lastly, a small helper used to facilitate logging in the demos
// log promise helper
const logp = p=>
p.then(
x=> console.log("Done:", x),
err=> console.log("Error:", err.message)
);
Demo time! Here I'm going to dogfood my own implementation of mapSeries
to run each demo in sequential order!.
Because mapSeries
excepts to be called on a Promise, I kick off each demo with 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()));
Go ahead, run the demo now
// 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()));
EDIT
I'm reapproaching this problem as a series of promises should be considered like a chain or composition of promises. Each resolve promise will feed it's value to the next promise.
Per @Zhegan's remarks, it makes more sense for the series
function to take an array of promise creators, otherwise there's no way to guarantee the promises would run in serial. If you pass an array of Promises, each promise will immediately run its executor and start doing work. Thus, there's no way that the work of Promise 2 could depend on the completed work of Promise 1.
Per @Bergi's remarks, my previous answer was a little weird. I think this update makes things a little more consistent.
Promise series without error
// ([(a-> (Promise b)), (b-> (Promise c)]), ...]) -> a -> (Promise c)
Promise.series = function series(tasks) {
return x=>
tasks.reduce((a,b)=> a.then(b), Promise.resolve(x));
};
// a -> [a] -> (Promise [a])
var concatp = x=> xs=>
new Promise((resolve, reject)=>
setTimeout(() => {
console.log(xs, x);
if (xs.length < 3)
resolve(xs.concat([x]));
else
reject(Error('too many items'));
}, 250));
var done = (x)=> console.log('done:', x);
var err = (e)=> console.log('error:', e.message);
Promise.series([concatp(3), concatp(6), concatp(9)]) ([]) .then(done, err);
// [] 3
// [ 3 ] 6
// [ 3, 6 ] 9
// done: [ 3, 6, 9 ]
Promise series with an error
// ([(a-> (Promise b)), (b-> (Promise c)]), ...]) -> a -> (Promise c)
Promise.series = function series(tasks) {
return x=>
tasks.reduce((a,b)=> a.then(b), Promise.resolve(x));
};
// a -> [a] -> (Promise [a])
var concatp = x=> xs=>
new Promise((resolve, reject)=>
setTimeout(() => {
console.log(xs, x);
if (xs.length < 3)
resolve(xs.concat([x]));
else
reject(Error('too many items'));
}, 250));
var done = (x)=> console.log('done:', x);
var err = (e)=> console.log('error:', e.message);
Promise.series([concatp(3), concatp(6), concatp(9), concatp(12)]) ([]) .then(done, err);
// [] 3
// [ 3 ] 6
// [ 3, 6 ] 9
// [ 3, 6, 9 ] 12
// error: too many items
这篇关于实施Promise.series作为Promise.all的替代的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!