实施Promise.series作为Promise.all的替代 [英] Implementing Promise.series as alternative to Promise.all

查看:1000
本文介绍了实施Promise.series作为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系列没有错误



< class =snippet-code-js lang-js prettyprint-override> //([(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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆