Cách tốt nhất để hạn chế sự đồng thời khi sử dụng Promise.all () của ES6 là gì?


98

Tôi có một số mã đang lặp lại danh sách đã được truy vấn từ cơ sở dữ liệu và thực hiện yêu cầu HTTP cho từng phần tử trong danh sách đó. Danh sách đó đôi khi có thể là một con số khá lớn (hàng nghìn) và tôi muốn đảm bảo rằng tôi không gặp phải một máy chủ web có hàng nghìn yêu cầu HTTP đồng thời.

Phiên bản viết tắt của mã này hiện trông giống như thế này ...

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 */});

Mã này đang chạy trên Node 4.3.2. Để nhắc lại, có thể Promise.allđược quản lý để chỉ một số Hứa hẹn nhất định được thực hiện tại bất kỳ thời điểm nào không?



3
Đừng quên rằng điều Promise.allđó quản lý sự tiến triển của lời hứa - bản thân những lời hứa sẽ làm được điều đó, Promise.allchỉ cần chờ đợi chúng.
Bergi


Câu trả lời:


51

Lưu ý rằng điều Promise.all()đó không kích hoạt những lời hứa bắt đầu công việc của họ, mà chính việc tạo ra lời hứa sẽ có.

Với ý nghĩ đó, một giải pháp sẽ là kiểm tra bất cứ khi nào một lời hứa được giải quyết, liệu một lời hứa mới có nên được bắt đầu hay không hay bạn đã đến giới hạn chưa.

Tuy nhiên, thực sự không cần phải phát minh lại bánh xe ở đây. Một thư viện mà bạn có thể sử dụng cho mục đích này làes6-promise-pool . Từ các ví dụ của họ:

// On the Web, leave out this line and use the script tag above instead. 
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)
})

25
Thật không may khi es6-promise-pool phát minh lại Promise thay vì sử dụng chúng. Tôi đề nghị giải pháp ngắn gọn này để thay thế (nếu bạn đang sử dụng ES6 hoặc ES7 đã) github.com/rxaviers/async-pool
Rafael Xavier

3
Hãy xem xét cả hai, async-pool có vẻ tốt hơn! Thẳng về phía trước hơn và nhẹ hơn.
Vô tận

2
Tôi cũng nhận thấy giới hạn p là cách triển khai đơn giản nhất. Xem ví dụ của tôi dưới đây. stackoverflow.com/a/52262024/8177355
Matthew Rideout

2
Tôi nghĩ rằng tiny-asyc-pool là giải pháp tốt hơn nhiều, không xâm phạm và khá tự nhiên để hạn chế sự đồng thời của các lời hứa.
Sunny Tambi

73

Giới hạn P

Tôi đã so sánh giới hạn đồng thời của lời hứa với một tập lệnh tùy chỉnh, bluebird, es6-promise-pool và p-limit. Tôi tin rằng giới hạn p có cách triển khai đơn giản nhất, được rút gọn cho nhu cầu này. Xem tài liệu của họ .

Yêu cầu

Để tương thích với async chẳng hạn

Ví dụ của tôi

Trong ví dụ này, chúng ta cần chạy một hàm cho mọi URL trong mảng (như, có thể là một yêu cầu API). Đây được gọi là fetchData(). Nếu chúng ta có một loạt hàng nghìn mục cần xử lý, thì tính đồng thời chắc chắn sẽ hữu ích để tiết kiệm tài nguyên CPU và bộ nhớ.

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);
})();

Kết quả nhật ký bảng điều khiển là một mảng dữ liệu phản hồi lời hứa đã giải quyết của bạn.


4
Cảm ơn vì điều này! Cái này đơn giản hơn nhiều
John

3
Đây là thư viện tốt nhất mà tôi từng thấy để hạn chế các yêu cầu đồng thời. Và ví dụ tuyệt vời, cảm ơn!
Chris Livdahl

2
Cảm ơn vì đã thực hiện so sánh. Bạn đã so sánh với github.com/rxaviers/async-pool chưa?
ahong

1
Dễ sử dụng, sự lựa chọn tuyệt vời.
drmrbrewer

22

Sử dụng Array.prototype.splice

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

2
Đây là một giải pháp được đánh giá thấp. Yêu thích sự đơn giản.
Brannon

8
Điều này chạy các chức năng theo lô thay vì nhóm, trong đó một chức năng được gọi ngay lập tức khi một chức năng khác kết thúc.
cltsang

Yêu thích giải pháp này!
prasun

mất một chút thời gian để nắm bắt những gì nó đang làm với việc thiếu nhiều bối cảnh xung quanh nó, chẳng hạn như nó đang chạy một đợt thay vì một nhóm. Bạn đang sắp xếp lại thứ tự của mảng mỗi khi bạn nối từ đầu hoặc ở giữa. (trình duyệt phải lập chỉ mục lại tất cả các mục) một hiệu suất lý thuyết tốt hơn thay vì lấy những thứ từ cuối thay vào đó arr.splice(-100)nếu liều đặt hàng không phù hợp, có thể bạn có thể đảo ngược mảng: P
Endless

Rất hữu ích để chạy theo lô. Lưu ý: đợt tiếp theo sẽ không bắt đầu cho đến khi đợt hiện tại hoàn thành 100%.
Casey Dwayne

20

Nếu bạn biết cách hoạt động của trình vòng lặp và cách chúng được sử dụng, bạn sẽ không cần thêm bất kỳ thư viện nào, vì bạn có thể rất dễ dàng tự xây dựng đồng thời của riêng mình. Hãy để tôi chứng minh:

/* [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)
  }
}

Chúng ta có thể sử dụng cùng một trình lặp và chia sẻ nó giữa các công nhân.

Nếu bạn đã sử dụng .entries()thay vì .values()bạn sẽ tạo ra một mảng 2D [[index, value]]mà tôi sẽ minh họa bên dưới với đồng thời là 2

const sleep = t => new Promise(rs => setTimeout(rs, t))

async function doWork(iterator) {
  for (let [index, item] of iterator) {
    await sleep(1000)
    console.log(index + ': ' + item)
  }
}

const iterator = Array.from('abcdefghij').entries()
const workers = new Array(2).fill(iterator).map(doWork)
//    ^--- starts two workers sharing the same iterator

Promise.allSettled(workers).then(() => console.log('done'))

Lợi ích của việc này là bạn có thể có một chức năng tạo thay vì có mọi thứ sẵn sàng cùng một lúc.


Lưu ý: sự khác biệt của điều này so với ví dụ async-pool là nó sinh ra hai công nhân, vì vậy nếu một công nhân đưa ra lỗi vì lý do nào đó ở chỉ số 5, nó sẽ không ngăn công nhân kia làm phần còn lại. Vì vậy, bạn đi từ thực hiện 2 đồng thời xuống 1. (vì vậy nó sẽ không dừng lại ở đó) Vì vậy, lời khuyên của tôi là bạn bắt tất cả các lỗi bên trong doWorkhàm


Điều này thật tuyệt! Cảm ơn Endless!
user3413723

Đây chắc chắn là một cách tiếp cận tuyệt vời! Chỉ cần đảm bảo rằng sự đồng thời của bạn không vượt quá độ dài của danh sách nhiệm vụ của bạn (nếu bạn quan tâm đến kết quả) vì bạn có thể kết thúc với các tính năng bổ sung!
Kris Oye

Điều gì đó có thể trở nên thú vị hơn sau này là khi Luồng được hỗ trợ Readable.from (iterator) . Chrome đã làm cho các luồng có thể chuyển được . vì vậy bạn có thể tạo các luồng có thể đọc được và gửi nó đến nhân viên web và tất cả chúng sẽ kết thúc bằng cách sử dụng cùng một trình lặp bên dưới.
Endless

16

Bluebird's Promise.map có thể sử dụng tùy chọn đồng thời để kiểm soát số lượng các lời hứa sẽ chạy song song. Đôi khi nó dễ dàng hơn .allvì bạn không cần phải tạo mảng lời hứa.

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
}

bluebird là grate nếu u cần nhanh hơn lời hứa và ~ 18kb thêm rác nếu bạn chỉ sử dụng nó cho một điều;)
Endless

1
Tất cả phụ thuộc vào mức độ quan trọng của một thứ đối với bạn và nếu có cách khác nhanh hơn / dễ dàng hơn. Một sự đánh đổi điển hình. Tôi sẽ chọn dễ sử dụng và hoạt động trên vài kb, nhưng YMMV.
Jingshao Chen

11

Thay vì sử dụng các hứa hẹn để hạn chế các yêu cầu http, hãy sử dụng http.Agent.maxSockets tích hợp sẵn của nút . Điều này loại bỏ yêu cầu sử dụng thư viện hoặc viết mã tổng hợp của riêng bạn và có thêm lợi thế kiểm soát nhiều hơn những gì bạn đang hạn chế.

agent.maxSockets

Theo mặc định được đặt thành Vô cực. Xác định số lượng ổ cắm đồng thời mà tác nhân có thể mở trên mỗi điểm gốc. Nguồn gốc là kết hợp 'host: port' hoặc 'host: port: localAddress'.

Ví dụ:

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

Nếu đưa ra nhiều yêu cầu cho cùng một nguồn gốc, bạn cũng có thể đặt keepAlivethành true (xem tài liệu ở trên để biết thêm thông tin).


11
Tuy nhiên, việc tạo hàng nghìn lần đóng ngay lập tức và gộp các ổ cắm dường như không hiệu quả lắm?
Bergi

3

Tôi đề xuất thư viện async-pool: https://github.com/rxaviers/async-pool

npm install tiny-async-pool

Sự miêu tả:

Chạy nhiều chức năng trả về hứa hẹn & không đồng bộ với đồng thời hạn chế bằng cách sử dụng ES6 / ES7 gốc

asyncPool chạy nhiều chức năng trả về hứa hẹn & không đồng bộ trong một nhóm đồng thời hạn chế. Nó từ chối ngay lập tức ngay khi một trong những lời hứa từ chối. Nó sẽ giải quyết khi tất cả các lời hứa hoàn thành. Nó gọi hàm vòng lặp càng sớm càng tốt (dưới giới hạn đồng thời).

Sử dụng:

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]`.

1
Làm việc cho tôi. Cảm ơn. Đây là thư viện tuyệt vời.
Sunny Tambi

2

Nó có thể được giải quyết bằng cách sử dụng đệ quy.

Ý tưởng là ban đầu bạn gửi số lượng yêu cầu tối đa được phép và mỗi yêu cầu này sẽ tiếp tục gửi một cách đệ quy khi hoàn thành.

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);
});

2

Đây là giải pháp ES7 của tôi để sao chép-dán thân thiện và có tính năng hoàn chỉnh Promise.all()/ map()thay thế, với giới hạn đồng thời.

Tương tự như Promise.all()nó duy trì thứ tự trả lại cũng như dự phòng cho các giá trị trả về không có hứa hẹn.

Tôi cũng bao gồm một so sánh về việc triển khai khác nhau vì nó minh họa một số khía cạnh mà một số giải pháp khác đã bỏ qua.

Sử dụng

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

Thực hiện

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();
  });
}

So sánh

// 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

Phần kết luận

asyncPool() nên là giải pháp tốt nhất vì nó cho phép các yêu cầu mới bắt đầu ngay sau khi bất kỳ yêu cầu nào trước đó kết thúc.

asyncBatch() được bao gồm dưới dạng so sánh vì việc triển khai nó dễ hiểu hơn, nhưng nó sẽ có hiệu suất chậm hơn vì tất cả các yêu cầu trong cùng một lô bắt buộc phải hoàn thành để bắt đầu lô tiếp theo.

Trong ví dụ giả định này, vani không giới hạn Promise.all()tất nhiên là nhanh nhất, trong khi những cái khác có thể hoạt động tốt hơn trong một kịch bản tắc nghẽn thế giới thực.

Cập nhật

Thư viện async-pool mà những người khác đã đề xuất có lẽ là một giải pháp thay thế tốt hơn cho việc triển khai của tôi vì nó hoạt động gần như giống hệt nhau và có cách triển khai ngắn gọn hơn với cách sử dụng thông minh Promise.race (): https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js

Hy vọng rằng câu trả lời của tôi vẫn có thể phục vụ một giá trị giáo dục.


1

Dưới đây là ví dụ cơ bản về phát trực tuyến và 'giới hạn p'. Nó truyền luồng đọc http sang 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
            );
        }

0

Vì vậy, tôi đã cố gắng làm cho một số ví dụ được hiển thị hoạt động cho mã của tôi, nhưng vì điều này chỉ dành cho tập lệnh nhập chứ không phải mã sản xuất, sử dụng gói npm batch-hứa hẹn là con đường dễ dàng nhất đối với tôi

LƯU Ý: Yêu cầu thời gian chạy để hỗ trợ Promise hoặc được polyfilled.

Api batchPromises (int: batchSize, array: Collection, i => Promise: Iteratee) Lời hứa: Iteratee sẽ được gọi sau mỗi đợt.

Sử dụng:

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]
});


0

Đệ quy là câu trả lời nếu bạn không muốn sử dụng các thư viện bên ngoài

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); 
}

0

Đây là những gì tôi đã sử dụng Promise.race, bên trong mã của tôi ở đây

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
}

Nếu bạn muốn xem một ví dụ: https://jsfiddle.net/thecodermarcelo/av2tp83o/5/


2
Tôi sẽ không gọi đó là đồng thời ... Điều đó giống như thực thi hàng loạt ... Bạn làm 4 tác vụ, đợi tất cả hoàn thành và sau đó thực hiện 4. nếu một trong số chúng giải quyết sớm, bạn vẫn đợi 3 tác vụ còn lại hoàn thành , những gì bạn nên sử dụng làPromise.race
Endless

2
một cái gì đó tương tự như thế này: github.com/rxaviers/async-pool/blob/master/lib/es7.js
Endless

0
  • Câu trả lời của @tcooc khá tuyệt. Không biết về nó và sẽ tận dụng nó trong tương lai.
  • Tôi cũng rất thích câu trả lời của @MatthewRideout , nhưng nó sử dụng thư viện bên ngoài !!

Bất cứ khi nào có thể, tôi cố gắng phát triển những thứ này một mình, thay vì đi tìm thư viện. Bạn sẽ học được rất nhiều khái niệm mà trước đây có vẻ khó khăn.

Các bạn nghĩ gì về nỗ lực này:
(Tôi đã suy nghĩ rất nhiều và tôi nghĩ rằng nó đang hoạt động, nhưng hãy chỉ ra nếu nó không hoặc có điều gì đó sai cơ bản)

 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}`))

Cách tiếp cận này cung cấp một API đẹp, tương tự như các nhóm luồng trong scala / java.
Sau khi tạo một phiên bản của pool với const cappedPool = new Pool(2), bạn chỉ cần cung cấp các lời hứa cho nó cappedPool.add(() => myPromise).
Rõ ràng là chúng ta phải đảm bảo rằng lời hứa không bắt đầu ngay lập tức và đó là lý do tại sao chúng ta phải "cung cấp nó một cách lười biếng" với sự trợ giúp của hàm.

Quan trọng nhất, hãy lưu ý rằng kết quả của phương pháp add là một Lời hứa sẽ được hoàn thành / giải quyết với giá trị của lời hứa ban đầu của bạn ! Điều này làm cho việc sử dụng rất trực quan.

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

0

Thật không may là không có cách nào để làm điều đó với Promise.all bản địa, vì vậy bạn phải sáng tạo.

Đây là cách nhanh nhất ngắn gọn nhất mà tôi có thể tìm thấy mà không cần sử dụng bất kỳ thư viện bên ngoài nào.

Nó sử dụng một tính năng javascript mới hơn được gọi là trình lặp. Trình lặp về cơ bản theo dõi những mục nào đã được xử lý và những gì chưa.

Để sử dụng nó trong mã, bạn tạo một mảng các hàm không đồng bộ. Mỗi hàm không đồng bộ yêu cầu cùng một trình lặp cho mục tiếp theo cần được xử lý. Mỗi chức năng xử lý không đồng bộ mục của chính nó và khi hoàn thành sẽ yêu cầu trình vòng lặp cho một mục mới. Khi trình lặp hết các mục, tất cả các chức năng sẽ hoàn thành.

Cảm ơn @Endless về nguồn cảm hứng.

var items = [
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
    "https://www.stackoverflow.com",
];

var 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));
    }
})


Tò mò tại sao điều này được đánh dấu xuống. Nó rất giống với những gì tôi đã nghĩ ra.
Kris Oye

0

Rất nhiều giải pháp tốt. Tôi bắt đầu với giải pháp thanh lịch được đăng bởi @Endless và kết thúc với phương pháp mở rộng nhỏ này không sử dụng bất kỳ thư viện bên ngoài nào cũng như không chạy theo lô (mặc dù giả sử bạn có các tính năng như async, v.v.):

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);

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.