给定一个异步函数列表,我如何一次处理它们?

时间:2018-02-28 10:11:49

标签: javascript promise async-await

JavaScript提供了一种收集承诺列表的机制:

const tasks = [ fetch('a'), fetch('b'), fetch('c') ];

const results = await Promise.all(tasks); 

但是,如果我想执行所有任务,但只有n同时执行,该怎么办?

const results = await MorePromise.allWithConcurrency(2, tasks); // 2 at a time

JavaScript是否提供此类功能?如果没有,是否有任何强大的库?

2 个答案:

答案 0 :(得分:2)

用纯JS ES6怎么样??

var ps      = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3), Promise.resolve(4), Promise.resolve(5)],
    groupBy = (n,a) => a.reduce((r,p,i) => !(i%n) ? (r.push([p]),r) : (r[r.length-1].push(p),r),[]);

groupBy(2,ps).map(sps => Promise.all(sps).then(console.log));
.as-console-wrapper {
max-height: 100% !important;
}

根据@GrégoryNEUT的正确评论我还有两个选择,以防你有一个数据数组来调用承诺返回函数(我使用bring模仿fetch这里)

var bring   = (url,data) => new Promise((v,x) => setTimeout(v, ~~(Math.random()*500), data)); // echoes data in 0~500 ms
    urls    = ["url0", "url1","url2","url3","url4"],
    groupBy = (n,a) => a.reduce((r,u,i) => !(i%n) ? (r.push([u]),r) : (r[r.length-1].push(u),r),[]);

groupBy(2,urls).reduce((p,sus,i) => i ? p.then(r => (console.log(`Here I am doing something with the fetched data from urls ${r}`),
                                                     Promise.all(sus.map(u => bring(u,u.replace(/[^\d]+/,""))))))
                                      : Promise.all(sus.map(u => bring(u,u.replace(/[^\d]+/,"")))), Promise.resolve())
               .then(r => console.log(`Here I am doing something with the fetched data from urls ${r}`), e => console.log(e));

或者使用spread&的一个小巧时髦的Haskellesque递归方法rest运算符,例如;

var bring   = (url,data) => new Promise((v,x) => setTimeout(v, ~~(Math.random()*500), data)); // echoes data in 0~500 ms
    urls    = ["url0", "url1","url2","url3","url4"],
    groupBy = (n,a) => a.reduce((r,u,i) => !(i%n) ? (r.push([u]),r) : (r[r.length-1].push(u),r),[]),
    seqPrms = (sus,...suss) => Promise.all(sus.map(u => bring(u,u.replace(/[^\d]+/,""))))
                                      .then(r => (console.log(`Here I am doing something with the fetched data from urls ${r}`),
                                                  suss.length && seqPrms(...suss)),
                                            e => console.log(e));

seqPrms(...groupBy(2,urls));

答案 1 :(得分:0)

您可以使用bluebird这样的插件来实现它,function允许它:example here

const Promise = require('bluebird');
const _ = require('lodash');

let tasks = [...]; // over 10,000 items.

Promise.all(
  _(tasks).map(task => {
    return task.doAsyncThing();
  })
    .value();
);

Promise.map(
  tasks, 
  task => {
    return task.doAsyncThing();
  }, 
  { concurrency: 1000 }
);

您也可以自己创建一个能够处理它的功能。这是我自己编写的一个功能,它是可以改进的。



const resultPromises = [];

let nextToCall = 0;

let errorHappened = false;

/**
 * Function that get called when one promise got executed
 */
function callbackOnePromiseGotExecuted({
  error,
  result,
  allExecutedCallback,
  array,
}) {
  resultPromises.push(result);

  // Do nothing if an error got reported
  if (errorHappened) {
    return;
  }

  // Return the error
  if (error) {
    allExecutedCallback(null, resultPromises);

    errorHappened = true;

    return;
  }

  // Check if it was the last promise to execute
  if (resultPromises.length === array.length) {
    allExecutedCallback(null, resultPromises);

    return;
  }

  nextToCall += 1;

  // Stop if we already acalled everything
  if (nextToCall > array.length) return;

  // If it wasn't call a new promise
  array[nextToCall - 1].call()
    .then(ret => callbackOnePromiseGotExecuted({
      error: null,
      result: ret,
      allExecutedCallback,
      array,
    }))
    .catch(e => callbackOnePromiseGotExecuted({
      error: e,
      result: null,
      allExecutedCallback,
      array,
    }));
}

/**
 * Handle the call of multiple promise with concurrency
 */
function promiseWithConcurrencyCallback({
  array,
  concurrencyNumber,
  allExecutedCallback,
}) {
  for (let i = 0; i < concurrencyNumber; ++i) {
    array[nextToCall].call()
      .then(ret => callbackOnePromiseGotExecuted({
        error: null,
        result: ret,
        allExecutedCallback,
        array,
      }))
      .catch(e => callbackOnePromiseGotExecuted({
        error: e,
        result: null,
        allExecutedCallback,
        array,
      }));

    nextToCall += 1;
  }

}

function promiseWithConcurrency(array, concurrencyNumber) {
  return new Promise((resolve, reject) => {
    promiseWithConcurrencyCallback({
      array,
      concurrencyNumber,
      allExecutedCallback: (error, result) => {
        if (error) return reject(error);

        return resolve(result);
      },
    });
  });
}

const array = [
  () => new Promise((resolve) => resolve('01')),
  () => new Promise((resolve) => resolve('02')),
  () => new Promise((resolve) => resolve('03')),
  () => new Promise((resolve) => resolve('04')),
  () => new Promise((resolve) => resolve('05')),
  () => new Promise((resolve) => resolve('06')),
];

promiseWithConcurrency(array, 2)
  .then(rets => console.log('rets', rets))
  .catch(error => console.log('error', error));
&#13;
&#13;
&#13;

编辑关于我对@Redu帖子的评论

&#13;
&#13;
var ps      = [new Promise((resolve) => { console.log('A'); setTimeout(() => { resolve(1) }, 5000); }), Promise.resolve(2), Promise.resolve(3), Promise.resolve(4), new Promise((resolve) => { console.log('B'); setTimeout(() => { resolve(5) }, 5000); })],
    groupBy = (n,a) => a.reduce((r,p,i) => !(i%n) ? (r.push([p]),r) : (r[r.length-1].push(p),r),[]);

groupBy(2,ps).map(sps => Promise.all(sps).then(console.log));
&#13;
&#13;
&#13;

正如您在我编辑过的代码段中看到的那样,console.log('A')console.log('B')会立即显示出来。这意味着函数同时执行,然后每两个解析两次。因此,如果目的是限制对资源的访问权限,那么您的解决方案就不会起作用。他们仍将在同一时间访问。