【问题标题】:ES6 Promise replacement of async.eachLimit / async.mapLimitES6 Promise 替换 async.eachLimit / async.mapLimit
【发布时间】:2017-10-09 02:11:57
【问题描述】:

async 中,如果我需要对 1000 个项目应用异步函数,我可以这样做:

async.mapLimit(items, 10, (item, callback) => {
    foo(item, callback);
});

这样只能同时处理 10 个项目,从而限制开销并允许控制。

有了 ES6 承诺,虽然我可以轻松做到:

Promise.all(items.map((item) => {
    return bar(item);
}));

这将同时处理所有 1000 个项目,这可能会导致很多问题。

我知道Bluebird have ways to handle that,但我正在寻找 ES6 解决方案。

【问题讨论】:

标签: javascript ecmascript-6 promise es6-promise


【解决方案1】:

如果你不关心结果,那么很快就搞定一个:

Promise.eachLimit = async (funcs, limit) => {
  let rest = funcs.slice(limit);
  await Promise.all(funcs.slice(0, limit).map(async func => {
    await func();
    while (rest.length) {
      await rest.shift()();
    }
  }));
};

// Demo:

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));

async function foo(s) {
  await wait(Math.random() * 2000);
  console.log(s);
}

(async () => {
  let funcs = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map(s => () => foo(s));
  await Promise.eachLimit(funcs, 5);
})();

一个关键性能属性是在任何函数完成后立即运行下一个可用函数。

保留结果

按顺序保留结果可能会使其不那么优雅,但还不错:

Promise.mapLimit = async (funcs, limit) => {
  let results = [];
  await Promise.all(funcs.slice(0, limit).map(async (func, i) => {
    results[i] = await func();
    while ((i = limit++) < funcs.length) {
      results[i] = await funcs[i]();
    }
  }));
  return results;
};

// Demo:

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));

async function foo(s) {
  await wait(Math.random() * 2000);
  console.log(s);
  return s.toLowerCase();
}

(async () => {
  let funcs = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map(s => () => foo(s));
  console.log((await Promise.mapLimit(funcs, 5)).join(""));
})();

【讨论】:

  • 对于不熟悉 await/async 表示法的任何人,您可以复制/粘贴定义(在 Demo 上方),然后像往常一样与 .then() 一起正常使用。跨度>
  • 您是否不需要将let funcs = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map(s =&gt; () =&gt; foo(s)); 行中的映射函数重写为:... .map(s =&gt; async () =&gt; await(foo));
  • 我认为保留结果的版本存在错误。我多次处理数组中的同一个项目。
  • @chovy 现在已修复。当 limit 在包含 await 的 for 循环中递增时,我很草率。感谢您发现它!
  • 什么是 promise.eachlimit?
【解决方案2】:

没有内置任何内容,但您当然可以自己将它们分组到 Promise 链中,并在生成的链数组上使用 Promise.all

const items = /* ...1000 items... */;
const concurrencyLimit = 10;
const promise = Promise.all(items.reduce((promises, item, index) => {
    // What chain do we add it to?
    const chainNum = index % concurrencyLimit;
    let chain = promises[chainNum];
    if (!chain) {
        // New chain
        chain = promises[chainNum] = Promise.resolve();
    }
    // Add it
    promises[chainNum] = chain.then(_ => foo(item));
    return promises;
}, []));

这是一个示例,显示了在任何给定时间有多少并发承诺(并且还显示了每个“链”何时完成,并且只执行 200 而不是 1,000):

const items = buildItems();
const concurrencyLimit = 10;
const promise = Promise.all(items.reduce((promises, item, index) => {
    const chainNum = index % concurrencyLimit;
    let chain = promises[chainNum];
    if (!chain) {
        chain = promises[chainNum] = Promise.resolve();
    }
    promises[chainNum] = chain.then(_ => foo(item));
    return promises;
}, []).map(chain => chain.then(_ => console.log("Chain done"))));
promise.then(_ => console.log("All done"));

function buildItems() {
  const items = [];
  for (let n = 0; n < 200; ++n) {
    items[n] = n;
  }
  return items;
}

var outstanding = 0;
function foo(item) {
  ++outstanding;
  console.log("Starting " + item + " (" + outstanding + ")");
  return new Promise(resolve => {
    setTimeout(_ => {
      --outstanding;
      console.log("Resolving " + item + " (" + outstanding + ")");
      resolve(item);
    }, Math.random() * 500);
  });
}
.as-console-wrapper {
  max-height: 100% !important;
}

我应该注意,如果你想跟踪每一个的结果,你必须修改上面的;它不会尝试跟踪结果(!)。 :-)

【讨论】:

  • 遗憾的是,没有任何东西可以用于此,异步不支持承诺,但非常有用:/
  • @DrakaSAN:没有内置。 :-) 我确定有图书馆可以做到这一点,比如 Bluebird 或 Q 或类似的(不过我还没有检查过)。
【解决方案3】:

使用Array.prototype.splice

while (funcs.length) {
  await Promise.all( funcs.splice(0, 100).map(f => f()) )
}

【讨论】:

    【解决方案4】:

    这是最接近 async.eachLimit 的一个

    Promise.eachLimit = async (coll, limit, asyncFunc) => {
    let ret = [];
        const splitArr = coll.reduce((acc,item,i)=> (i%limit) ? acc :[...acc,coll.slice(i,i+limit)],[])
        for(let i =0; i< splitArr.length;i++){
            ret[i]=await Promise.all(splitArr[i].map(ele=>asyncFunc(ele)));
        }
        return ret;
    }
    
    const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
    
    async function foo(s) {
      await wait(Math.random() * 2000);
      console.log(s);
      return s.toLowerCase();
    }
    
    (async () => {
      let arr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
      console.log((await Promise.eachLimit(arr, 5, foo)));
    })();

    【讨论】:

      猜你喜欢
      • 2016-07-05
      • 2019-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-18
      • 1970-01-01
      • 2015-06-06
      • 2016-08-04
      相关资源
      最近更新 更多