简体   繁体   English

使用 ES6 的 Promise.all() 时限制并发的最佳方法是什么?

[英]What is the best way to limit concurrency when using ES6's Promise.all()?

I have some code that is iterating over a list that was queried out of a database and making an HTTP request for each element in that list.我有一些代码迭代从数据库中查询出的列表,并对该列表中的每个元素发出 HTTP 请求。 That list can sometimes be a reasonably large number (in the thousands), and I would like to make sure I am not hitting a web server with thousands of concurrent HTTP requests.该列表有时可能是一个相当大的数字(数千),我想确保我不会使用数千个并发 HTTP 请求访问 web 服务器。

An abbreviated version of this code currently looks something like this...此代码的缩写版本目前看起来像这样......

function getCounts() {
  return users.map(user => {
    return new Promise(resolve => {
      remoteServer.getCount(user) // makes an HTTP request
      .then(() => {
        /* snip */
        resolve();
      });
    });
  });
}

Promise.all(getCounts()).then(() => { /* snip */});

This code is running on Node 4.3.2.此代码在 Node 4.3.2 上运行。 To reiterate, can Promise.all be managed so that only a certain number of Promises are in progress at any given time?重申一下,是否可以管理Promise.all以便在任何给定时间只有一定数量的 Promise 在进行中?

P-Limit P-极限

I have compared promise concurrency limitation with a custom script, bluebird, es6-promise-pool, and p-limit.我将 promise 并发限制与自定义脚本、bluebird、es6-promise-pool 和 p-limit 进行了比较。 I believe that p-limit has the most simple, stripped down implementation for this need.我相信p-limit有最简单、最精简的实现来满足这种需求。 See their documentation .请参阅他们的文档

Requirements要求

To be compatible with async in example与示例中的异步兼容

My Example我的例子

In this example, we need to run a function for every URL in the array (like, maybe an API request).在这个例子中,我们需要为数组中的每个 URL 运行一个函数(比如,可能是一个 API 请求)。 Here this is called fetchData() .这里称为fetchData() If we had an array of thousands of items to process, concurrency would definitely be useful to save on CPU and memory resources.如果我们有一个包含数千个项目的数组要处理,并发对于节省 CPU 和内存资源肯定是有用的。

const pLimit = require('p-limit');

// Example Concurrency of 3 promise at once
const limit = pLimit(3);

let urls = [
    "http://www.exampleone.com/",
    "http://www.exampletwo.com/",
    "http://www.examplethree.com/",
    "http://www.examplefour.com/",
]

// Create an array of our promises using map (fetchData() returns a promise)
let promises = urls.map(url => {

    // wrap the function we are calling in the limit function we defined above
    return limit(() => fetchData(url));
});

(async () => {
    // Only three promises are run at once (as defined above)
    const result = await Promise.all(promises);
    console.log(result);
})();

The console log result is an array of your resolved promises response data.控制台日志结果是您已解决的承诺响应数据的数组。

Using Array.prototype.splice使用Array.prototype.splice

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

Note that Promise.all() doesn't trigger the promises to start their work, creating the promise itself does.请注意, Promise.all()不会触发 Promise 开始工作,而创建 Promise 本身会触发。

With that in mind, one solution would be to check whenever a promise is resolved whether a new promise should be started or whether you're already at the limit.考虑到这一点,一种解决方案是在解决承诺时检查是否应该开始新的承诺,或者您是否已经达到极限。

However, there is really no need to reinvent the wheel here.但是,这里真的没有必要重新发明轮子。 One library that you could use for this purpose is es6-promise-pool .您可以为此目的使用的一个库是es6-promise-pool From their examples:从他们的例子中:

var PromisePool = require('es6-promise-pool')
 
var promiseProducer = function () {
  // Your code goes here. 
  // If there is work left to be done, return the next work item as a promise. 
  // Otherwise, return null to indicate that all promises have been created. 
  // Scroll down for an example. 
}
 
// The number of promises to process simultaneously. 
var concurrency = 3
 
// Create a pool. 
var pool = new PromisePool(promiseProducer, concurrency)
 
// Start the pool. 
var poolPromise = pool.start()
 
// Wait for the pool to settle. 
poolPromise.then(function () {
  console.log('All promises fulfilled')
}, function (error) {
  console.log('Some promise rejected: ' + error.message)
})

If you know how iterators work and how they are consumed you would't need any extra library, since it can become very easy to build your own concurrency yourself.如果您知道迭代器是如何工作的以及它们是如何被使用的,那么您就不需要任何额外的库,因为您自己构建自己的并发会变得非常容易。 Let me demonstrate:让我演示一下:

 /* [Symbol.iterator]() is equivalent to .values() const iterator = [1,2,3][Symbol.iterator]() */ const iterator = [1,2,3].values() // loop over all items with for..of for (const x of iterator) { console.log('x:', x) // notices how this loop continues the same iterator // and consumes the rest of the iterator, making the // outer loop not logging any more x's for (const y of iterator) { console.log('y:', y) } }

We can use the same iterator and share it across workers.我们可以使用相同的迭代器并在工作人员之间共享它。

If you had used .entries() instead of .values() you would have goten a 2D array with [[index, value]] which i will demonstrate below with a concurrency of 2如果您使用.entries()而不是.values()您将获得一个带有[[index, value]]的二维数组,我将在下面以 2 的并发性进行演示

 const sleep = t => new Promise(rs => setTimeout(rs, t)) const iterator = Array.from('abcdefghij').entries() // const results = [] || Array(someLength) async function doWork (iterator) { for (let [index, item] of iterator) { await sleep(1000) console.log(index + ': ' + item) // in case you need to store the results in order // results[index] = item + item // or if the order dose not mather // results.push(item + item) } } const workers = Array(2).fill(iterator).map(doWork) // ^--- starts two workers sharing the same iterator Promise.allSettled(workers).then(console.log.bind(null, 'done'))

The benefit of this is that you can have a generator function instead of having everything ready at once.这样做的好处是您可以拥有一个生成器功能,而不是一次准备好所有东西。

What's even more awesome is that you can do stream.Readable.from(iterator) in node (and eventually in whatwg streams as well).更棒的是,您可以在 node 中执行stream.Readable.from(iterator) (最终也可以在 whatwg 流中)。 and with transferable ReadbleStream, this makes this potential very useful in the feature if you are working with web workers also for performances并且使用可转移的 ReadbleStream,如果您正在与网络工作者一起工作以进行表演,这使得该功能在该功能中非常有用


Note: the different from this compared to example async-pool is that it spawns two workers, so if one worker throws an error for some reason at say index 5 it won't stop the other worker from doing the rest.注意:与示例 异步池相比,与此不同的是它产生了两个工作人员,因此如果一个工作人员由于某种原因在索引 5 处抛出错误,它不会阻止另一个工作人员完成其余工作。 So you go from doing 2 concurrency down to 1. (so it won't stop there) So my advise is that you catch all errors inside the doWork function所以你从 2 并发到 1。(所以它不会停在那里)所以我的建议是你在doWork函数中捕获所有错误

Instead of using promises for limiting http requests, use node's built-in http.Agent.maxSockets .不要使用 promises 来限制 http 请求,而是使用 node 的内置http.Agent.maxSockets This removes the requirement of using a library or writing your own pooling code, and has the added advantage more control over what you're limiting.这消除了使用库或编写自己的池代码的要求,并具有额外的优势,可以更好地控制您所限制的内容。

agent.maxSockets agent.maxSockets

By default set to Infinity.默认设置为无穷大。 Determines how many concurrent sockets the agent can have open per origin.确定代理可以为每个源打开多少个并发套接字。 Origin is either a 'host:port' or 'host:port:localAddress' combination. Origin 是“host:port”或“host:port:localAddress”组合。

For example:例如:

var http = require('http');
var agent = new http.Agent({maxSockets: 5}); // 5 concurrent connections per origin
var request = http.request({..., agent: agent}, ...);

If making multiple requests to the same origin, it might also benefit you to set keepAlive to true (see docs above for more info).如果向同一个来源发出多个请求,将keepAlive设置为 true 也可能使您受益(有关更多信息,请参阅上面的文档)。

bluebird's Promise.map can take a concurrency option to control how many promises should be running in parallel. bluebird 的Promise.map可以采用并发选项来控制应并行运行的 Promise 数量。 Sometimes it is easier than .all because you don't need to create the promise array.有时它比.all更容易,因为您不需要创建 promise 数组。

const Promise = require('bluebird')

function getCounts() {
  return Promise.map(users, user => {
    return new Promise(resolve => {
      remoteServer.getCount(user) // makes an HTTP request
      .then(() => {
        /* snip */
        resolve();
       });
    });
  }, {concurrency: 10}); // <---- at most 10 http requests at a time
}

As all others in this answer thread have pointed out, Promise.all() won't do the right thing if you need to limit concurrency.正如此答案线程中的所有其他人所指出的那样,如果您需要限制并发性, Promise.all()将不会做正确的事情。 But ideally you shouldn't even want to wait until all of the Promises are done before processing them.但理想情况下,您甚至应该等到所有Promise 都完成后再处理它们。

Instead, you want to process each result ASAP as soon as it becomes available, so you don't have to wait for the very last promise to finish before you start iterating over them.相反,您希望在每个结果可用时尽快处理它,因此您不必等待最后一个 Promise 完成后再开始迭代它们。

So, here's a code sample that does just that, based partly on the answer by Endless and also on this answer by TJ Crowder .所以,这是一个代码示例,它部分基于Endless 的答案以及 TJ Crowder 的这个答案

 // example tasks that sleep and return a number // in real life, you'd probably fetch URLs or something const tasks = []; for (let i = 0; i < 20; i++) { tasks.push(async () => { console.log(`start ${i}`); await sleep(Math.random() * 1000); console.log(`end ${i}`); return i; }); } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } (async () => { for await (let value of runTasks(3, tasks.values())) { console.log(`output ${value}`); } })(); async function* runTasks(maxConcurrency, taskIterator) { // Each async iterator is a worker, polling for tasks from the shared taskIterator // Sharing the iterator ensures that each worker gets unique tasks. const asyncIterators = new Array(maxConcurrency); for (let i = 0; i < maxConcurrency; i++) { asyncIterators[i] = (async function* () { for (const task of taskIterator) yield await task(); })(); } yield* raceAsyncIterators(asyncIterators); } async function* raceAsyncIterators(asyncIterators) { async function nextResultWithItsIterator(iterator) { return { result: await iterator.next(), iterator: iterator }; } /** @type Map<AsyncIterator<T>, Promise<{result: IteratorResult<T>, iterator: AsyncIterator<T>}>> */ const promises = new Map(asyncIterators.map(iterator => [iterator, nextResultWithItsIterator(iterator)])); while (promises.size) { const { result, iterator } = await Promise.race(promises.values()); if (result.done) { promises.delete(iterator); } else { promises.set(iterator, nextResultWithItsIterator(iterator)); yield result.value; } } }

There's a lot of magic in here;这里有很多魔法; let me explain.让我解释。

This solution is built around async generator functions , which many JS developers may not be familiar with.这个解决方案是围绕异步生成器函数构建的,许多 JS 开发人员可能并不熟悉。

A generator function (aka function* function) returns a "generator," an iterator of results. 生成器函数(又名function*函数)返回“生成器”,即结果的迭代器。 Generator functions are allowed to use the yield keyword where you might have normally used a return keyword.生成器函数可以在通常使用return关键字的地方使用yield关键字。 The first time a caller calls next() on the generator (or uses a for...of loop), the function* function runs until it yield sa value;当调用者第一次在生成器上调用next()时(或使用for...of循环), function*函数将一直运行,直到它yield sa 值; that becomes the next() value of the iterator.这成为迭代器的next()值。 But the subsequent time next() is called, the generator function resumes from the yield statement, right where it left off, even if it's in the middle of a loop.但随后调用next()时,生成器函数从yield语句恢复,就在它停止的地方,即使它在循环的中间。 (You can also yield* , to yield all of the results of another generator function.) (您也可以yield* ,以产生另一个生成器函数的所有结果。)

An "async generator function" ( async function* ) is a generator function that returns an "async iterator," which is an iterator of promises. “异步生成器函数”( async function* )是一个生成器函数,它返回一个“异步迭代器”,它是一个 Promise 的迭代器。 You can call for await...of on an async iterator.您可以在异步迭代器上调用for await...of Async generator functions can use the await keyword, as you might do in any async function .异步生成器函数可以使用await关键字,就像在任何async function中一样。

In the example, we call runTasks() with an array of task functions.在示例中,我们使用一组任务函数调用runTasks() runTasks() is an async generator function, so we can call it with a for await...of loop. runTasks()是一个异步生成器函数,因此我们可以使用for await...of循环调用它。 Each time the loop runs, we'll process the result of the latest completed task.每次循环运行时,我们都会处理最新完成的任务的结果。

runTasks() creates N async iterators, the workers. runTasks()创建 N 个异步迭代器,即工人。 (Note that the workers are initially defined as async generator functions, but we immediately invoke each function, and store each resulting async iterator in the asyncIterators array.) The example calls runTasks with 3 concurrent workers, so no more than 3 tasks are launched at the same time. (请注意,worker 最初被定义为异步生成器函数,但我们立即调用每个函数,并将每个生成的异步迭代器存储在asyncIterators数组中。)该示例调用具有 3 个并发 worker 的runTasks ,因此启动的任务不超过 3 个同时。 When any task completes, we immediately queue up the next task.当任何任务完成时,我们立即将下一个任务排队。 (This is superior to "batching", where you do 3 tasks at once, await all three of them, and don't start the next batch of three until the entire previous batch has finished.) (这优于“批处理”,您一次执行 3 个任务,等待所有三个任务,并且在整个前一批完成之前不要开始下一批三个。)

runTasks() concludes by "racing" its async iterators with yield* raceAsyncIterators() . runTasks()通过使用yield* raceAsyncIterators() “竞速”它的异步迭代器来结束。 raceAsyncIterators() is like Promise.race() but it races N iterators of Promises instead of just N Promises; raceAsyncIterators()类似于Promise.race()但它竞争 N 个 Promises 迭代器,而不仅仅是 N 个 Promises; it returns an async iterator that yields the results of resolved Promises.它返回一个异步迭代器,该迭代器产生已解决的 Promise 的结果。

raceAsyncIterators() starts by defining a promises Map from each of the iterators to promises. raceAsyncIterators()首先定义一个从每个迭代器到 Promise 的promises Map Each promise is a promise for an iteration result along with the iterator that generated it.每个承诺都是对迭代结果的承诺以及生成它的迭代器。

With the promises map, we can Promise.race() the values of the map, giving us the winning iteration result and its iterator.使用promises映射,我们可以Promise.race()映射的值,为我们提供获胜的迭代结果及其迭代器。 If the iterator is completely done , we remove it from the map;如果迭代器完全done ,我们将其从地图中移除; otherwise we replace its Promise in the promises map with the iterator's next() Promise and yield result.value .否则,我们用迭代器的next() Promise 和yield result.value替换promises映射中的 Promise。

In conclusion, runTasks() is an async generator function that yields the results of racing N concurrent async iterators of tasks, so the end user can just for await (let value of runTasks(3, tasks.values())) to process each result as soon as it becomes available.总之, runTasks()是一个异步生成器函数,它产生 N 个并发异步任务迭代器的结果,因此最终用户可以只for await (let value of runTasks(3, tasks.values()))来处理每个结果一旦可用。

I suggest the library async-pool: https://github.com/rxaviers/async-pool我建议库异步池: https ://github.com/rxaviers/async-pool

npm install tiny-async-pool

Description:描述:

Run multiple promise-returning & async functions with limited concurrency using native ES6/ES7使用原生 ES6/ES7 以有限的并发运行多个 promise-returning & async 函数

asyncPool runs multiple promise-returning & async functions in a limited concurrency pool. asyncPool 在有限的并发池中运行多个 promise-returning & async 函数。 It rejects immediately as soon as one of the promises rejects.一旦其中一个承诺被拒绝,它就会立即拒绝。 It resolves when all the promises completes.它在所有承诺完成时解决。 It calls the iterator function as soon as possible (under concurrency limit).它尽快(在并发限制下)调用迭代器函数。

Usage:用法:

const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
await asyncPool(2, [1000, 5000, 3000, 2000], timeout);
// Call iterator (i = 1000)
// Call iterator (i = 5000)
// Pool limit of 2 reached, wait for the quicker one to complete...
// 1000 finishes
// Call iterator (i = 3000)
// Pool limit of 2 reached, wait for the quicker one to complete...
// 3000 finishes
// Call iterator (i = 2000)
// Itaration is complete, wait until running ones complete...
// 5000 finishes
// 2000 finishes
// Resolves, results are passed in given array order `[1000, 5000, 3000, 2000]`.

Here is my ES7 solution to a copy-paste friendly and feature complete Promise.all() / map() alternative, with a concurrency limit.这是我的 ES7 解决方案,对复制粘贴友好且功能完整Promise.all() / map()替代方案,具有并发限制。

Similar to Promise.all() it maintains return order as well as a fallback for non promise return values.Promise.all()类似,它维护返回顺序以及非承诺返回值的后备。

I also included a comparison of the different implementation as it illustrates some aspects a few of the other solutions have missed.我还对不同的实现进行了比较,因为它说明了其他一些解决方案遗漏的一些方面。

Usage用法

const asyncFn = delay => new Promise(resolve => setTimeout(() => resolve(), delay));
const args = [30, 20, 15, 10];
await asyncPool(args, arg => asyncFn(arg), 4); // concurrency limit of 4

Implementation执行

async function asyncBatch(args, fn, limit = 8) {
  // Copy arguments to avoid side effects
  args = [...args];
  const outs = [];
  while (args.length) {
    const batch = args.splice(0, limit);
    const out = await Promise.all(batch.map(fn));
    outs.push(...out);
  }
  return outs;
}

async function asyncPool(args, fn, limit = 8) {
  return new Promise((resolve) => {
    // Copy arguments to avoid side effect, reverse queue as
    // pop is faster than shift
    const argQueue = [...args].reverse();
    let count = 0;
    const outs = [];
    const pollNext = () => {
      if (argQueue.length === 0 && count === 0) {
        resolve(outs);
      } else {
        while (count < limit && argQueue.length) {
          const index = args.length - argQueue.length;
          const arg = argQueue.pop();
          count += 1;
          const out = fn(arg);
          const processOut = (out, index) => {
            outs[index] = out;
            count -= 1;
            pollNext();
          };
          if (typeof out === 'object' && out.then) {
            out.then(out => processOut(out, index));
          } else {
            processOut(out, index);
          }
        }
      }
    };
    pollNext();
  });
}

Comparison比较

// A simple async function that returns after the given delay
// and prints its value to allow us to determine the response order
const asyncFn = delay => new Promise(resolve => setTimeout(() => {
  console.log(delay);
  resolve(delay);
}, delay));

// List of arguments to the asyncFn function
const args = [30, 20, 15, 10];

// As a comparison of the different implementations, a low concurrency
// limit of 2 is used in order to highlight the performance differences.
// If a limit greater than or equal to args.length is used the results
// would be identical.

// Vanilla Promise.all/map combo
const out1 = await Promise.all(args.map(arg => asyncFn(arg)));
// prints: 10, 15, 20, 30
// total time: 30ms

// Pooled implementation
const out2 = await asyncPool(args, arg => asyncFn(arg), 2);
// prints: 20, 30, 15, 10
// total time: 40ms

// Batched implementation
const out3 = await asyncBatch(args, arg => asyncFn(arg), 2);
// prints: 20, 30, 20, 30
// total time: 45ms

console.log(out1, out2, out3); // prints: [30, 20, 15, 10] x 3

// Conclusion: Execution order and performance is different,
// but return order is still identical

Conclusion结论

asyncPool() should be the best solution as it allows new requests to start as soon as any previous one finishes. asyncPool()应该是最好的解决方案,因为它允许在前一个请求完成后立即启动新请求。

asyncBatch() is included as a comparison as its implementation is simpler to understand, but it should be slower in performance as all requests in the same batch is required to finish in order to start the next batch.包含asyncBatch()作为比较,因为它的实现更易于理解,但它的性能应该更慢,因为同一批次中的所有请求都需要完成才能开始下一批。

In this contrived example, the non-limited vanilla Promise.all() is of course the fastest, while the others could perform more desirable in a real world congestion scenario.在这个人为的例子中,非限制的 vanilla Promise.all()当然是最快的,而其他的在现实世界的拥塞场景中可能表现得更理想。

Update更新

The async-pool library that others have already suggested is probably a better alternative to my implementation as it works almost identically and has a more concise implementation with a clever usage of Promise.race(): https://github.com/rxaviers/async-pool/blob/master/lib/es7.js其他人已经建议的异步池库可能是我实现的更好替代方案,因为它的工作原理几乎相同,并且通过巧妙地使用 Promise.race() 实现更简洁: https ://github.com/rxaviers/ 异步池/blob/master/lib/es7.js

Hopefully my answer can still serve an educational value.希望我的回答仍然具有教育价值。

Semaphore is well known concurrency primitive that was designed to solve similar problems.信号量是众所周知的并发原语,旨在解决类似问题。 It's very universal construct, implementations of Semaphore exist in many languages.它是非常通用的构造,信号量的实现存在于多种语言中。 This is how one would use Semaphore to solve this issue:这就是使用 Semaphore 解决此问题的方式:

async function main() {
  const s = new Semaphore(100);
  const res = await Promise.all(
    entities.map((users) => 
      s.runExclusive(() => remoteServer.getCount(user))
    )
  );
  return res;
}

I'm using implementation of Semaphore from async-mutex , it has decent documentation and TypeScript support.我正在使用来自async-mutex的 Semaphore 实现,它有不错的文档和 TypeScript 支持。

If you want to dig deep into topics like this you can take a look at the book "The Little Book of Semaphores" which is freely available asPDF here如果您想深入研究此类主题,可以查看“信号量小书”一书,该书可在此处以 PDF 格式免费获得

Here goes basic example for streaming and 'p-limit'.这是流式传输和“p-limit”的基本示例。 It streams http read stream to mongo db.它将 http 读取流传输到 mongo db。

const stream = require('stream');
const util = require('util');
const pLimit = require('p-limit');
const es = require('event-stream');
const streamToMongoDB = require('stream-to-mongo-db').streamToMongoDB;


const pipeline = util.promisify(stream.pipeline)

const outputDBConfig = {
    dbURL: 'yr-db-url',
    collection: 'some-collection'
};
const limit = pLimit(3);

async yrAsyncStreamingFunction(readStream) => {
        const mongoWriteStream = streamToMongoDB(outputDBConfig);
        const mapperStream = es.map((data, done) => {
                let someDataPromise = limit(() => yr_async_call_to_somewhere())

                    someDataPromise.then(
                        function handleResolve(someData) {

                            data.someData = someData;    
                            done(null, data);
                        },
                        function handleError(error) {
                            done(error)
                        }
                    );
                })

            await pipeline(
                readStream,
                JSONStream.parse('*'),
                mapperStream,
                mongoWriteStream
            );
        }

It can be resolved using recursion.可以使用递归来解决。

The idea is that initially you send maximum allowed number of requests and each of these requests should recursively continue to send itself on its completion.这个想法是,最初您发送最大允许数量的请求,并且这些请求中的每一个都应该在完成时递归地继续发送自己。

function batchFetch(urls, concurrentRequestsLimit) {
    return new Promise(resolve => {
        var documents = [];
        var index = 0;

        function recursiveFetch() {
            if (index === urls.length) {
                return;
            }
            fetch(urls[index++]).then(r => {
                documents.push(r.text());
                if (documents.length === urls.length) {
                    resolve(documents);
                } else {
                    recursiveFetch();
                }
            });
        }

        for (var i = 0; i < concurrentRequestsLimit; i++) {
            recursiveFetch();
        }
    });
}

var sources = [
    'http://www.example_1.com/',
    'http://www.example_2.com/',
    'http://www.example_3.com/',
    ...
    'http://www.example_100.com/'
];
batchFetch(sources, 5).then(documents => {
   console.log(documents);
});
  • @tcooc 's answer was quite cool. @tcooc的回答很酷。 Didn't know about it and will leverage it in the future.不知道它,将来会利用它。
  • I also enjoyed @MatthewRideout 's answer, but it uses an external library!!我也很喜欢@MatthewRideout的回答,但它使用了一个外部库!!

Whenever possible, I give a shot at developing this kind of things on my own, rather than going for a library.只要有可能,我都会尝试自己开发这种东西,而不是去图书馆。 You end up learning a lot of concepts which seemed daunting before.你最终会学习很多以前看起来令人生畏的概念。

 class Pool{ constructor(maxAsync) { this.maxAsync = maxAsync; this.asyncOperationsQueue = []; this.currentAsyncOperations = 0 } runAnother() { if (this.asyncOperationsQueue.length > 0 && this.currentAsyncOperations < this.maxAsync) { this.currentAsyncOperations += 1; this.asyncOperationsQueue.pop()() .then(() => { this.currentAsyncOperations -= 1; this.runAnother() }, () => { this.currentAsyncOperations -= 1; this.runAnother() }) } } add(f){ // the argument f is a function of signature () => Promise this.runAnother(); return new Promise((resolve, reject) => { this.asyncOperationsQueue.push( () => f().then(resolve).catch(reject) ) }) } } //####################################################### // TESTS //####################################################### function dbCall(id, timeout, fail) { return new Promise((resolve, reject) => { setTimeout(() => { if (fail) { reject(`Error for id ${id}`); } else { resolve(id); } }, timeout) } ) } const dbQuery1 = () => dbCall(1, 5000, false); const dbQuery2 = () => dbCall(2, 5000, false); const dbQuery3 = () => dbCall(3, 5000, false); const dbQuery4 = () => dbCall(4, 5000, true); const dbQuery5 = () => dbCall(5, 5000, false); const cappedPool = new Pool(2); const dbQuery1Res = cappedPool.add(dbQuery1).catch(i => i).then(i => console.log(`Resolved: ${i}`)) const dbQuery2Res = cappedPool.add(dbQuery2).catch(i => i).then(i => console.log(`Resolved: ${i}`)) const dbQuery3Res = cappedPool.add(dbQuery3).catch(i => i).then(i => console.log(`Resolved: ${i}`)) const dbQuery4Res = cappedPool.add(dbQuery4).catch(i => i).then(i => console.log(`Resolved: ${i}`)) const dbQuery5Res = cappedPool.add(dbQuery5).catch(i => i).then(i => console.log(`Resolved: ${i}`))

This approach provides a nice API, similar to thread pools in scala/java.这种方法提供了一个很好的 API,类似于 scala/java 中的线程池。
After creating one instance of the pool with const cappedPool = new Pool(2) , you provide promises to it with simply cappedPool.add(() => myPromise) .在使用const cappedPool = new Pool(2)创建池的一个实例后,您只需cappedPool.add(() => myPromise)向它提供承诺。
Obliviously we must ensure that the promise does not start immediately and that is why we must "provide it lazily" with the help of the function.不知不觉我们必须确保承诺不会立即开始,这就是为什么我们必须在函数的帮助下“懒惰地提供它”。

Most importantly, notice that the result of the method add is a Promise which will be completed/resolved with the value of your original promise !最重要的是,请注意方法add的结果是一个 Promise ,它将使用您原始 promise 的值完成/解决 This makes for a very intuitive use.这使得使用非常直观。

const resultPromise = cappedPool.add( () => dbCall(...))
resultPromise
.then( actualResult => {
   // Do something with the result form the DB
  }
)

The concurrent function below will return a Promise which resolves to an array of resolved promise values, while implementing a concurrency limit.下面的concurrent function 将返回一个 Promise,它解析为一个已解析 promise 值的数组,同时实现并发限制。 No 3rd party library.没有第 3 方库。

 // waits 50 ms then resolves to the passed-in arg const sleepAndResolve = s => new Promise(rs => setTimeout(()=>rs(s), 50)) // queue 100 promises const funcs = [] for(let i=0; i<100; i++) funcs.push(()=>sleepAndResolve(i)) //run the promises with a max concurrency of 10 concurrent(10,funcs).then(console.log) // prints [0,1,2...,99].catch(()=>console.log("there was an error")) /** * Run concurrent promises with a maximum concurrency level * @param concurrency The number of concurrently running promises * @param funcs An array of functions that return promises * @returns a promise that resolves to an array of the resolved values from the promises returned by funcs */ function concurrent(concurrency, funcs) { return new Promise((resolve, reject) => { let index = -1; const p = []; for (let i = 0; i < Math.max(1, Math.min(concurrency, funcs.length)); i++) runPromise(); function runPromise() { if (++index < funcs.length) (p[p.length] = funcs[index]()).then(runPromise).catch(reject); else if (index === funcs.length) Promise.all(p).then(resolve).catch(reject); } }); }

Here's the Typescript version if you are interested如果您有兴趣,这里是 Typescript 版本

/**
 * Run concurrent promises with a maximum concurrency level
 * @param concurrency The number of concurrently running promises
 * @param funcs An array of functions that return promises
 * @returns a promise that resolves to an array of the resolved values from the promises returned by funcs
 */
function concurrent<V>(concurrency:number, funcs:(()=>Promise<V>)[]):Promise<V[]> {
  return new Promise((resolve,reject)=>{
    let index = -1;
    const p:Promise<V>[] = []
    for(let i=0; i<Math.max(1,Math.min(concurrency, funcs.length)); i++) runPromise()
    function runPromise() {
      if (++index < funcs.length) (p[p.length] = funcs[index]()).then(runPromise).catch(reject)
      else if (index === funcs.length) Promise.all(p).then(resolve).catch(reject)
    }
  })
}

So I tried to make some examples shown work for my code, but since this was only for an import script and not production code, using the npm package batch-promises was surely the easiest path for me因此,我尝试为我的代码制作一些示例,但由于这仅适用于导入脚本而不是生产代码,因此使用 npm 包batch-promises对我来说肯定是最简单的路径

NOTE: Requires runtime to support Promise or to be polyfilled.注意:需要运行时来支持 Promise 或被 polyfill。

Api batchPromises(int: batchSize, array: Collection, i => Promise: Iteratee) The Promise: Iteratee will be called after each batch. Api batchPromises(int: batchSize, array: Collection, i => Promise: Iteratee) 每个批次后都会调用 Promise: Iteratee。

Use:利用:

 batch-promises Easily batch promises NOTE: Requires runtime to support Promise or to be polyfilled. Api batchPromises(int: batchSize, array: Collection, i => Promise: Iteratee) The Promise: Iteratee will be called after each batch. Use: import batchPromises from 'batch-promises'; batchPromises(2, [1,2,3,4,5], i => new Promise((resolve, reject) => { // The iteratee will fire after each batch resulting in the following behaviour: // @ 100ms resolve items 1 and 2 (first batch of 2) // @ 200ms resolve items 3 and 4 (second batch of 2) // @ 300ms resolve remaining item 5 (last remaining batch) setTimeout(() => { resolve(i); }, 100); })) .then(results => { console.log(results); // [1,2,3,4,5] });

Recursion is the answer if you don't want to use external libraries如果您不想使用外部库,递归就是答案

downloadAll(someArrayWithData){
  var self = this;

  var tracker = function(next){
    return self.someExpensiveRequest(someArrayWithData[next])
    .then(function(){
      next++;//This updates the next in the tracker function parameter
      if(next < someArrayWithData.length){//Did I finish processing all my data?
        return tracker(next);//Go to the next promise
      }
    });
  }

  return tracker(0); 
}

Unfortunately there is no way to do it with native Promise.all, so you have to be creative.不幸的是,原生 Promise.all 无法做到这一点,所以你必须要有创意。

This is the quickest most concise way I could find without using any outside libraries.这是我在不使用任何外部库的情况下能找到的最快最简洁的方法。

It makes use of a newer javascript feature called an iterator.它利用了一个新的 JavaScript 特性,称为迭代器。 The iterator basically keeps track of what items have been processed and what haven't.迭代器基本上跟踪哪些项目已处理,哪些尚未处理。

In order to use it in code, you create an array of async functions.为了在代码中使用它,您需要创建一个异步函数数组。 Each async function asks the same iterator for the next item that needs to be processed.每个异步函数都向同一个迭代器询问下一个需要处理的项目。 Each function processes its own item asynchronously, and when done asks the iterator for a new one.每个函数异步处理自己的项目,完成后向迭代器询问一个新项目。 Once the iterator runs out of items, all the functions complete.一旦迭代器用完项目,所有功能就完成了。

Thanks to @Endless for inspiration.感谢@Endless 的启发。

 const items = [ 'https://httpbin.org/bytes/2', 'https://httpbin.org/bytes/2', 'https://httpbin.org/bytes/2', 'https://httpbin.org/bytes/2', 'https://httpbin.org/bytes/2', 'https://httpbin.org/bytes/2', 'https://httpbin.org/bytes/2', 'https://httpbin.org/bytes/2' ] const concurrency = 5 Array(concurrency).fill(items.entries()).map(async (cursor) => { for (let [index, url] of cursor){ console.log('getting url is ', index, url) // run your async task instead of this next line var text = await fetch(url).then(res => res.text()) console.log('text is', text.slice(0, 20)) } })

So many good solutions.这么多好的解决方案。 I started out with the elegant solution posted by @Endless and ended up with this little extension method that does not use any external libraries nor does it run in batches (although assumes you have features like async, etc):我从@Endless 发布的优雅解决方案开始,最终得到了这个不使用任何外部库也不批量运行的小扩展方法(尽管假设您具有异步等功能):

Promise.allWithLimit = async (taskList, limit = 5) => {
    const iterator = taskList.entries();
    let results = new Array(taskList.length);
    let workerThreads = new Array(limit).fill(0).map(() => 
        new Promise(async (resolve, reject) => {
            try {
                let entry = iterator.next();
                while (!entry.done) {
                    let [index, promise] = entry.value;
                    try {
                        results[index] = await promise;
                        entry = iterator.next();
                    }
                    catch (err) {
                        results[index] = err;
                    }
                }
                // No more work to do
                resolve(true); 
            }
            catch (err) {
                // This worker is dead
                reject(err);
            }
        }));

    await Promise.all(workerThreads);
    return results;
};

 Promise.allWithLimit = async (taskList, limit = 5) => { const iterator = taskList.entries(); let results = new Array(taskList.length); let workerThreads = new Array(limit).fill(0).map(() => new Promise(async (resolve, reject) => { try { let entry = iterator.next(); while (!entry.done) { let [index, promise] = entry.value; try { results[index] = await promise; entry = iterator.next(); } catch (err) { results[index] = err; } } // No more work to do resolve(true); } catch (err) { // This worker is dead reject(err); } })); await Promise.all(workerThreads); return results; }; const demoTasks = new Array(10).fill(0).map((v,i) => new Promise(resolve => { let n = (i + 1) * 5; setTimeout(() => { console.log(`Did nothing for ${n} seconds`); resolve(n); }, n * 1000); })); var results = Promise.allWithLimit(demoTasks);

expanding on the answer posted by @deceleratedcaviar, I created a 'batch' utility function that takes as argument: array of values, concurrency limit and processing function.扩展@deceleratedcaviar 发布的答案,我创建了一个“批处理”实用函数,它以参数为参数:值数组、并发限制和处理函数。 Yes I realize that using Promise.all this way is more akin to batch processing vs true concurrency, but if the goal is to limit excessive number of HTTP calls at one time I go with this approach due to its simplicity and no need for external library.是的,我意识到使用 Promise.all 这种方式更类似于批处理与真正的并发性,但如果目标是一次限制过多的 HTTP 调用,我会选择这种方法,因为它简单且不需要外部库.

 async function batch(o) { let arr = o.arr let resp = [] while (arr.length) { let subset = arr.splice(0, o.limit) let results = await Promise.all(subset.map(o.process)) resp.push(results) } return [].concat.apply([], resp) } let arr = [] for (let i = 0; i < 250; i++) { arr.push(i) } async function calc(val) { return val * 100 } (async () => { let resp = await batch({ arr: arr, limit: 100, process: calc }) console.log(resp) })();

One more solution with a custom promise library ( CPromise ):使用自定义承诺库 ( CPromise ) 的另一种解决方案:

    import { CPromise } from "c-promise2";
    import cpFetch from "cp-fetch";
    
    const promise = CPromise.all(
      function* () {
        const urls = [
          "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=1",
          "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=2",
          "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=3",
          "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=4",
          "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=5",
          "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=6",
          "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=7"
        ];
    
        for (const url of urls) {
          yield cpFetch(url); // add a promise to the pool
          console.log(`Request [${url}] completed`);
        }
      },
      { concurrency: 2 }
    ).then(
      (v) => console.log(`Done: `, v),
      (e) => console.warn(`Failed: ${e}`)
    );
    
    // yeah, we able to cancel the task and abort pending network requests
    // setTimeout(() => promise.cancel(), 4500);

    import { CPromise } from "c-promise2";
    import cpFetch from "cp-fetch";
    
    const promise = CPromise.all(
      [
        "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=1",
        "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=2",
        "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=3",
        "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=4",
        "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=5",
        "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=6",
        "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s&x=7"
      ],
      {
        mapper: (url) => {
          console.log(`Request [${url}]`);
          return cpFetch(url);
        },
        concurrency: 2
      }
    ).then(
      (v) => console.log(`Done: `, v),
      (e) => console.warn(`Failed: ${e}`)
    );
    
    // yeah, we able to cancel the task and abort pending network requests
    //setTimeout(() => promise.cancel(), 4500);

Warning this has not been benchmarked for efficiency and does a lot of array copying/creation警告这还没有以效率为基准,并且做了很多数组复制/创建

If you want a more functional approach you could do something like:如果您想要更实用的方法,您可以执行以下操作:

import chunk from 'lodash.chunk';

const maxConcurrency = (max) => (dataArr, promiseFn) =>
  chunk(dataArr, max).reduce(
      async (agg, batch) => [
          ...(await agg),
          ...(await Promise.all(batch.map(promiseFn)))
      ],
      []
  );

and then to you could use it like:然后你可以像这样使用它:

const randomFn = (data) =>
    new Promise((res) => setTimeout(
      () => res(data + 1),
        Math.random() * 1000
      ));


const result = await maxConcurrency(5)(
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    randomFn
);
console.log('result+++', result);

I had been using the bottleneck library, which I actually really liked, but in my case wasn't releasing memory and kept tanking long running jobs... Which isn't great for running the massive jobs that you likely want a throttling/concurrency library for in the first place.我一直在使用瓶颈库,我真的很喜欢它,但在我的情况下,它没有释放内存并一直在处理长时间运行的作业......这对于运行你可能想要节流/并发的大量作业并不好首先是图书馆。

I needed a simple, low-overhead, easy to maintain solution.我需要一个简单、开销低、易于维护的解决方案。 I also wanted something that kept the pool topped up, rather than simply batching predefined chunks... In the case of a downloader, this will stop that n GB file from holding up your queue for minutes/hours at a time, even though the rest of the batch finished ages ago.我还想要让池保持充值的东西,而不是简单地批处理预定义的块......对于下载器,这将阻止n GB 文件一次阻止你的队列几分钟/小时,即使其余的批次在很久以前就完成了。

This is the Node.js v16+, no-dependency, async generator solution I've been using instead:这是我一直在使用的 Node.js v16+、无依赖、异步生成器解决方案:

 const promiseState = function( promise ) { // A promise could never resolve to a unique symbol unless it was in this scope const control = Symbol(); // This helps us determine the state of the promise... A little heavy, but it beats a third-party promise library. The control is the second element passed to Promise.race() since it will only resolve first if the promise being tested is pending. return Promise .race([ promise, control ]) .then( value => ( value === control ) ? 'pending' : 'fulfilled' ) .catch( () => 'rejected' ); } const throttle = async function* ( reservoir, promiseFunction, highWaterMark ) { let iterable = reservoir.splice( 0, highWaterMark ).map( item => promiseFunction( item ) ); while ( iterable.length > 0 ) { // When a promise has resolved we have space to top it up to the high water mark... await Promise.any( iterable ); const pending = []; const resolved = []; // This identifies the promise(s) that have resolved so that we can yield them for ( const currentValue of iterable ) { if ( await promiseState( currentValue ) === 'pending' ) { pending.push( currentValue ); } else { resolved.push( currentValue ); } } // Put the remaining promises back into iterable, and top it to the high water mark iterable = [ ...pending, ...reservoir.splice( 0, highWaterMark - pending.length ).map( value => promiseFunction( value ) ) ]; yield Promise.allSettled( resolved ); } } // This is just an example of what would get passed as "promiseFunction"... This can be the function that returns your HTTP request promises const getTimeout = delay => new Promise( (resolve, reject) => setTimeout(resolve, delay, delay) ); // This is just the async IIFE that bootstraps this example ( async () => { const test = [ 1000, 2000, 3000, 4000, 5000, 6000, 1500, 2500, 3500, 4500, 5500, 6500 ]; for await ( const timeout of throttle( test, getTimeout, 4 ) ) { console.log( timeout ); } } )();

I have solution with creating chunks and using .reduce function to wait each chunks promise.alls to be finished.我有创建块并使用 .reduce 函数等待每个块 promise.alls 完成的解决方案。 And also I add some delay if the promises have some call limits.如果承诺有一些调用限制,我也会添加一些延迟。

export function delay(ms: number) {
  return new Promise<void>((resolve) => setTimeout(resolve, ms));
}

export const chunk = <T>(arr: T[], size: number): T[][] => [
  ...Array(Math.ceil(arr.length / size)),
].map((_, i) => arr.slice(size * i, size + size * i));

const myIdlist = []; // all items
const groupedIdList = chunk(myIdList, 20); // grouped by 20 items

await groupedIdList.reduce(async (prev, subIdList) => {
  await prev;
  // Make sure we wait for 500 ms after processing every page to prevent overloading the calls.
  const data = await Promise.all(subIdList.map(myPromise));
  await delay(500);
}, Promise.resolve());

This solution uses an async generator to manage concurrent promises with vanilla javascript.这个解决方案使用异步生成器来管理带有 vanilla javascript 的并发承诺。 The throttle generator takes 3 arguments: throttle生成器有 3 个参数:

  • An array of values to be be supplied as arguments to a promise genrating function.要作为参数提供给 Promise 生成函数的值数组。 (eg An array of URLs.) (例如,一组 URL。)
  • A function that return a promise.一个返回承诺的函数。 (eg Returns a promise for an HTTP request.) (例如,返回一个 HTTP 请求的承诺。)
  • An integer that represents the maximum concurrent promises allowed.一个整数,表示允许的最大并发承诺。

Promises are only instantiated as required in order to reduce memory consumption. Promise 仅在需要时实例化,以减少内存消耗。 Results can be iterated over using a for await...of statement.可以使用for await...of语句迭代结果。

The example below provides a function to check promise state, the throttle async generator, and a simple function that return a promise based on setTimeout .下面的示例提供了一个检查 promise 状态的函数、throttle 异步生成器,以及一个基于setTimeout返回 promise 的简单函数。 The async IIFE at the end defines the reservoir of timeout values, sets the async iterable returned by throttle , then iterates over the results as they resolve.最后的async IIFE定义了超时值的存储库,设置了throttle返回的异步迭代,然后在结果解析时对其进行迭代。

If you would like a more complete example for HTTP requests, let me know in the comments.如果您想要更完整的 HTTP 请求示例,请在评论中告诉我。

Please note that Node.js 16+ is required in order async generators.请注意,异步生成器需要 Node.js 16+

 const promiseState = function( promise ) { const control = Symbol(); return Promise .race([ promise, control ]) .then( value => ( value === control ) ? 'pending' : 'fulfilled' ) .catch( () => 'rejected' ); } const throttle = async function* ( reservoir, promiseClass, highWaterMark ) { let iterable = reservoir.splice( 0, highWaterMark ).map( item => promiseClass( item ) ); while ( iterable.length > 0 ) { await Promise.any( iterable ); const pending = []; const resolved = []; for ( const currentValue of iterable ) { if ( await promiseState( currentValue ) === 'pending' ) { pending.push( currentValue ); } else { resolved.push( currentValue ); } } console.log({ pending, resolved, reservoir }); iterable = [ ...pending, ...reservoir.splice( 0, highWaterMark - pending.length ).map( value => promiseClass( value ) ) ]; yield Promise.allSettled( resolved ); } } const getTimeout = delay => new Promise( ( resolve, reject ) => { setTimeout(resolve, delay, delay); } ); ( async () => { const test = [ 1100, 1200, 1300, 10000, 11000, 9000, 5000, 6000, 3000, 4000, 1000, 2000, 3500 ]; const throttledRequests = throttle( test, getTimeout, 4 ); for await ( const timeout of throttledRequests ) { console.log( timeout ); } } )();

A good solution for controlling the maximum number of promises/requests is to split your list of requests into pages, and produce only requests for one page at a time.控制最大承诺/请求数的一个很好的解决方案是将请求列表拆分为页面,并且一次只生成一个页面的请求。

The example below makes use of iter-ops library:下面的示例使用了iter-ops库:

import {pipe, toAsync, map, page} from 'iter-ops';

const i = pipe(
    toAsync(users), // make it asynchronous
    page(10), // split into pages of 10 items in each
    map(p => Promise.all(p.map(u => u.remoteServer.getCount(u)))), // map into requests
    wait() // resolve each page in the pipeline
);

// below triggers processing page-by-page:

for await(const p of i) {
    //=> p = resolved page of data
}

This way it won't try to create more requests/promises than the size of one page.这样它就不会尝试创建比一页大小更多的请求/承诺。

Using tiny-async-pool ES9 for await...of API, you can do the following:使用tiny-async-pool ES9 for await...of API,您可以执行以下操作:

const asyncPool = require("tiny-async-pool");
const getCount = async (user) => ([user, remoteServer.getCount(user)]);
const concurrency = 2;

for await (const [user, count] of asyncPool(concurrency, users, getCount)) {
  console.log(user, count);
}

The above asyncPool function returns an async iterator that yields as soon as a promise completes (under concurrency limit) and it rejects immediately as soon as one of the promises rejects.上面的 asyncPool 函数返回一个异步迭代器,一旦 Promise 完成(在并发限制下),它就会产生,并且一旦其中一个 Promise 拒绝,它就会立即拒绝。

It is possible to limit requests to server by using https://www.npmjs.com/package/job-pipe可以使用https://www.npmjs.com/package/job-pipe限制对服务器的请求

Basically you create a pipe and tell it how many concurrent requests you want:基本上你创建一个管道并告诉它你想要多少并发请求:

const pipe = createPipe({ throughput: 6, maxQueueSize: Infinity })

Then you take your function which performs call and force it through the pipe to create a limited amount of calls at the same time:然后,您使用执行调用的函数并强制它通过管道同时创建有限数量的调用:

const makeCall = async () => {...}
const limitedMakeCall = pipe(makeCall)

Finally, you call this method as many times as you need as if it was unchanged and it will limit itself on how many parallel executions it can handle:最后,您可以根据需要多次调用此方法,就好像它没有改变一样,它将限制自己可以处理多少并行执行:

await limitedMakeCall()
await limitedMakeCall()
await limitedMakeCall()
await limitedMakeCall()
await limitedMakeCall()
....
await limitedMakeCall()

Profit.利润。

I suggest not downloading packages and not writing hundreds of lines of code:建议不要下载包,不要写几百行代码:

async function async_arr<T1, T2>(
    arr: T1[],
    func: (x: T1) => Promise<T2> | T2, //can be sync or async
    limit = 5
) {
    let results: T2[] = [];
    let workers = [];
    let current = Math.min(arr.length, limit);
    async function process(i) {
        if (i < arr.length) {
            results[i] = await Promise.resolve(func(arr[i]));
            await process(current++);
        }
    }
    for (let i = 0; i < current; i++) {
        workers.push(process(i));
    }
    await Promise.all(workers);
    return results;
}

Here's my recipe, based on killdash9's answer.这是我的食谱,基于 killdash9 的回答。 It allows to choose the behaviour on exceptions ( Promise.all vs Promise.allSettled ).它允许选择异常行为( Promise.allPromise.allSettled )。

// Given an array of async functions, runs them in parallel,
// with at most maxConcurrency simultaneous executions
// Except for that, behaves the same as Promise.all,
// unless allSettled is true, where it behaves as Promise.allSettled  

function concurrentRun(maxConcurrency = 10, funcs = [], allSettled = false) {
  if (funcs.length <= maxConcurrency) {
    const ps = funcs.map(f => f());
    return allSettled ? Promise.allSettled(ps) : Promise.all(ps);
  }
  return new Promise((resolve, reject) => {
    let idx = -1;
    const ps = new Array(funcs.length);
    function nextPromise() {
      idx += 1;
      if (idx < funcs.length) {
        (ps[idx] = funcs[idx]()).then(nextPromise).catch(allSettled ? nextPromise : reject);
      } else if (idx === funcs.length) {
        (allSettled ? Promise.allSettled(ps) : Promise.all(ps)).then(resolve).catch(reject);
      }
    }
    for (let i = 0; i < maxConcurrency; i += 1) nextPromise();
  });
}

I know there are a lot of answers already, but I ended up using a very simple, no library or sleep required, solution that uses only a few commands.我知道已经有很多答案,但我最终使用了一个非常简单、不需要库或睡眠的解决方案,它只使用了几个命令。 Promise.all() simply lets you know when all the promises passed to it are finalized. Promise.all() 只是让您知道传递给它的所有承诺何时完成。 So, you can check on the queue intermittently to see if it is ready for more work, if so, add more processes.因此,您可以间歇性地检查队列,看它是否准备好进行更多工作,如果是,则添加更多进程。

For example:例如:

// init vars
const batchSize = 5
const calls = []
// loop through data and run processes  
for (let [index, data] of [1,2,3].entries()) {
   // pile on async processes 
   calls.push(doSomethingAsyncWithData(data))
   // every 5th concurrent call, wait for them to finish before adding more
   if (index % batchSize === 0) await Promise.all(calls)
}
// clean up for any data to process left over if smaller than batch size
const allFinishedProcs = await Promise.all(calls)

This is what I did using Promise.race , inside my code here这就是我在这里的代码中使用Promise.race所做的

const identifyTransactions = async function() {
  let promises = []
  let concurrency = 0
  for (let tx of this.transactions) {
    if (concurrency > 4)
      await Promise.race(promises).then(r => { promises = []; concurrency = 0 })
    promises.push(tx.identifyTransaction())
    concurrency++
  }
  if (promises.length > 0)
    await Promise.race(promises) //resolve the rest
}

If you wanna see an example: https://jsfiddle.net/thecodermarcelo/av2tp83o/5/如果你想看一个例子: https ://jsfiddle.net/thecodermarcelo/av2tp83o/5/

If you goal is to slow down the Promise.all to avoid Rate limiting, or overloading:如果您的目标是减慢 Promise.all 以避免速率限制或过载:

Here's my implementation这是我的实现

async function promiseAllGentle(arr, batchSize = 5, sleep = 50) {
  let output = [];
  while (arr.length) {
    const batchResult = await Promise.all(arr.splice(0, batchSize));
    output = [...output, ...batchResult];
    await new Promise((res) => setTimeout(res, sleep));
  }
  return output;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM