Який найкращий спосіб обмежити паралельність при використанні ES6 Promise.all ()?


99

У мене є код, який переглядає список, який запитували з бази даних, і робить запит HTTP для кожного елемента в цьому списку. Цей список іноді може бути досить великим (у тисячах), і я хотів би переконатися, що я не потрапляю на веб-сервер з тисячами одночасних запитів HTTP.

Скорочена версія цього коду в даний час виглядає приблизно так ...

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

Цей код працює на вузлі 4.3.2. Повторюючи, чи Promise.allможна керувати таким чином, щоб у будь-який момент часу виконувалася лише певна кількість обіцянок?



3
Не забувайте, що Promise.allкерувати прогресією обіцянок - обіцянки це роблять самі, Promise.allпросто чекають їх.
Бергі,


Відповіді:


51

Зверніть увагу, що Promise.all()обіцянки не запускають свою роботу, а сама обіцянка спрацьовує.

З огляду на це, одним із рішень буде перевірка кожного разу, коли обіцянка вирішується, чи слід розпочинати нову обіцянку, чи ви вже на межі.

Однак насправді немає необхідності винаходити колесо тут. Одна бібліотека, яку ви можете використати для цієї мети, цеes6-promise-pool . З їх прикладів:

// 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
Прикро, що es6-promis-pool заново винаходить Promise замість того, щоб їх використовувати. Натомість я пропоную це коротке рішення (якщо ви вже використовуєте ES6 або ES7) github.com/rxaviers/async-pool
Рафаель Ксав’єр

3
Подивившись обидва, асинхронний пул виглядає набагато краще! Більш прямий вперед і легший.
Нескінченні

2
Я також виявив, що p-limit - найпростіша реалізація. Дивіться мій приклад нижче. stackoverflow.com/a/52262024/8177355
Метью Райдаут

2
Я думаю, що tiny-asyc-pool - набагато краще, ненав’язливе і досить природне рішення для обмеження одночасності обіцянок.
Сонячний Тамбі

74

Р-межа

Я порівняв обмеження одночасності обіцянок із користувацьким сценарієм, bluebird, es6-promis-pool та p-limit. Я вважаю, що p-limit має найпростішу, позбавлену реалізацію для цієї потреби. Дивіться їх документацію .

Вимоги

Для сумісності з асинхронізацією в прикладі

Мій приклад

У цьому прикладі нам потрібно запустити функцію для кожної URL-адреси в масиві (наприклад, можливо, запит API). Тут це називається fetchData(). Якби у нас був масив з тисяч елементів для обробки, одночасність однозначно була б корисною, щоб заощадити на ресурсах ЦП та пам'яті.

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

Результат журналу консолі - це масив ваших вирішених даних відповідей на обіцянки.


4
Дякую за це! Це набагато простіше
Джон

3
Це, безумовно, найкраща бібліотека, яку я бачив для обмеження одночасних запитів. І чудовий приклад, дякую!
Кріс Лівдал,

2
Дякуємо за порівняння. Ви порівнювали з github.com/rxaviers/async-pool ?
ahong

1
Простий у використанні, чудовий вибір.
drmrbrewer

22

Використовуючи Array.prototype.splice

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

2
Це недооцінене рішення. Любіть простоту.
Бреннон

8
Це запускає функції в партіях замість пулу, де одна функція негайно викликається, коли інша закінчується.
cltsang

Сподобалося це рішення!
prasun

зайняв секунду, щоб зрозуміти, що робить, не маючи більше контексту навколо, як, наприклад, пакет, замість пулу. Ви впорядковуєте масив щоразу, коли зрощуєте спочатку або посередині. (браузер повинен переіндексувати всі елементи) теоретична ефективність краща альтернатива полягає в тому, щоб брати речі з кінця, замість цього, arr.splice(-100)якщо замовлення не має значення, можливо, ви можете змінити масив: P
нескінченні

Дуже корисно для бігу партіями. Примітка: наступна партія не розпочнеться до завершення поточної партії на 100%.
Кейсі Дуейн,

21

Якщо ви знаєте, як працюють ітератори та як вони споживаються, вам не знадобиться жодна додаткова бібліотека, оскільки самостійно створити власну паралельність може стати дуже легко. Дозвольте мені продемонструвати:

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

Ми можемо використовувати один і той же ітератор і ділитися ним між працівниками.

Якби ви використовували .entries()замість .values()вас, ви отримали б 2D-масив, з [[index, value]]яким я продемонструю нижче паралельність 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'))

Перевага цього полягає в тому, що ви можете мати функцію генератора замість того, щоб готувати все одразу.


Примітка: відмінне від цього порівняно з прикладом async-pool полягає в тому, що він породжує двох робітників, тому, якщо один працівник викидає помилку з якихось причин, скажімо, індекс 5, це не зупинить іншого працівника робити все інше. Отже, ви переходите від виконання 2 паралельних дій до 1 (так що на цьому не зупиниться) Тож моя порада полягає в тому, що ви ловите всі помилки всередині doWorkфункції


Це круто! Дякую Нескінченні!
user3413723

Це, безумовно, крутий підхід! Просто переконайтеся, що ваша паралельність не перевищує довжини вашого списку завдань (якщо ви все одно дбаєте про результати), оскільки в кінцевому підсумку у вас можуть стати додаткові послуги!
Kris Oye,

Щось, що може бути крутіше пізніше, це коли Streams отримує підтримку Readable.from (ітератор) . Chrome вже зробили потоки передачі . щоб ви могли створювати читабельні потоки та надсилати їх веб-працівникам, і в кінцевому підсумку всі вони використовували б той самий базовий ітератор.
Нескінченні

16

Програма Promise.map bluebird може використовувати паралельний параметр, щоб контролювати, скільки обіцянок має виконуватися паралельно. Іноді це простіше, ніж .allтому, що не потрібно створювати масив обіцянок.

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 вітається, якщо вам потрібні швидші обіцянки та ~ 18 кб зайвого сміття, якщо ви використовуєте його лише для одного;)
Нескінченні

1
Все залежить від того, наскільки важлива одна річ для вас, і чи є інший швидший / простіший кращий спосіб. Типовий компроміс. Я виберу простоту використання та функціонування на декілька кб, але YMMV.
Jingshao Chen

11

Замість того, щоб використовувати обіцянки для обмеження http-запитів, використовуйте вбудований у вузол http.Agent.maxSockets . Це скасовує вимогу використання бібліотеки або написання власного коду об’єднання та надає додаткову перевагу - більший контроль над тим, що ви обмежуєте.

agent.maxSockets

За замовчуванням встановлено значення "Нескінченність". Визначає, скільки одночасних сокетів агент може мати відкритими для кожного джерела. Походження - це або комбінація 'host: port' або 'host: port: localAddress'.

Наприклад:

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

Якщо ви робите кілька запитів до одного джерела, вам також може бути корисно встановити keepAliveзначення true (для отримання додаткової інформації див. Документи вище).


11
Тим не менше, створювати тисячі закриттів одразу і об’єднувати розетки здається не дуже ефективним?
Бергі,

3

Я пропоную бібліотечний асинхронний пул: https://github.com/rxaviers/async-pool

npm install tiny-async-pool

Опис:

Запустіть кілька функцій, що повертають обіцянки та асинхронізацію, з обмеженою одночасністю, використовуючи власну ES6 / ES7

asyncPool запускає безліч функцій, що повертають обіцянки та async, у обмеженому пулі паралельності. Він відхиляє негайно, як тільки одна з обіцянок відхиляє. Це вирішує, коли всі обіцянки виконуються. Він викликає функцію ітератора якомога швидше (з обмеженням паралельності).

Використання:

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
Працює для мене. Дякую. Це чудова бібліотека.
Сонячний Тамбі

2

Це можна вирішити за допомогою рекурсії.

Ідея полягає в тому, що спочатку ви надсилаєте максимально дозволену кількість запитів, і кожен із цих запитів повинен рекурсивно продовжувати надсилати себе після його завершення.

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

Ось моє рішення ES7 для копіювання та вставлення, що містить повну Promise.all()/ map()альтернативну функцію з обмеженням паралельності.

Подібно до Promise.all()цього він підтримує порядок повернення, а також резервний для неперспективних значень повернення.

Я також включив порівняння різного впровадження, оскільки воно ілюструє деякі аспекти, які деякі інші рішення пропустили.

Використання

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

Впровадження

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

Порівняння

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

Висновок

asyncPool() повинно бути найкращим рішенням, оскільки воно дозволяє запускати нові запити, як тільки закінчується будь-який попередній.

asyncBatch() включено як порівняння, оскільки його реалізація простіша для розуміння, але вона повинна бути повільнішою у виконанні, оскільки всі запити в одній партії потрібно завершити, щоб розпочати наступну партію.

У цьому надуманому прикладі, без обмежень ваніль Promise.all(), звичайно, є найшвидшою, тоді як інші можуть виявитися більш бажаними в реальному сценарії заторів.

Оновлення

Бібліотека асинхронного пулу, яку вже пропонували інші, є, мабуть, кращою альтернативою моїй реалізації, оскільки вона працює майже однаково і має більш стислу реалізацію з розумним використанням Promise.race (): https://github.com/rxaviers/ async-pool / blob / master / lib / es7.js

Сподіваюся, моя відповідь все ще може мати освітню цінність.


1

Ось базовий приклад для потокового передавання та "p-limit". Він перетікає потік читання http до монго 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

Тому я спробував зробити так, щоб деякі приклади працювали для мого коду, але оскільки це стосувалося лише сценарію імпорту, а не виробничого коду, використання пакетних обіцянок пакета npm, безперечно, було для мене найпростішим

ПРИМІТКА. Потрібен час виконання, щоб підтримувати Promise або бути заповненим.

Api batchPromises (int: batchSize, array: Collection, i => Promise: Iteratee) Обіцянка: Iteratee буде викликатися після кожної партії.

Використання:

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

Рекурсія - це відповідь, якщо ви не хочете використовувати зовнішні бібліотеки

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

Це те, що я зробив, використовуючи 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
}

Якщо ви хочете переглянути приклад: https://jsfiddle.net/thecodermarcelo/av2tp83o/5/


2
Я б не називав це одночасністю ... Це більше схоже на пакетне виконання ... Ви виконуєте 4 завдання, чекаєте, поки всі закінчаться, а потім робите наступні 4. якщо одне з них вирішить достроково, ви все ще чекаєте закінчення інших 3 , що вам слід використовуватиPromise.race
Нескінченні


0
  • @tcooc була досить класною. Не знав про це і використає його в майбутньому.
  • Мені також сподобалася відповідь @MatthewRideout , але вона використовує зовнішню бібліотеку !!

Коли це можливо, я намагаюся самостійно розробляти подібні речі, а не йду в бібліотеку. Зрештою, ти вивчаєш багато понять, які раніше здавались страшними.

Що ви думаєте, хлопці, про цю спробу:
(Я багато думав над цим, і думаю, що вона працює, але все-таки вкажіть, чи ні, або щось принципово неправильне)

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

Цей підхід забезпечує приємний API, схожий на пули потоків у scala / java.
Після створення одного екземпляра пулу за допомогою const cappedPool = new Pool(2)ви просто даєте йому обіцянки cappedPool.add(() => myPromise).
Очевидно, ми повинні переконатися, що обіцянка не розпочнеться негайно, і саме тому ми повинні "надавати її ліниво" за допомогою функції.

Найголовніше, зауважте, що результатом методу add є Обіцянка, яка буде виконана / вирішена зі значенням вашої початкової обіцянки ! Це робить дуже інтуїтивним використання.

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

0

На жаль, це неможливо зробити за допомогою рідного Promise.all, тому вам слід бути творчим.

Це найшвидший і найкоротший спосіб, який я міг знайти, не використовуючи жодних зовнішніх бібліотек.

Він використовує новішу функцію javascript, яка називається ітератором. Ітератор в основному відстежує, які елементи були оброблені, а які ні.

Для того, щоб використовувати його в коді, ви створюєте масив асинхронних функцій. Кожна функція асинхронізації запитує той самий ітератор для наступного елемента, який потрібно обробити. Кожна функція асинхронно обробляє свій власний елемент, а після закінчення запитує у ітератора новий. Як тільки у ітератора закінчуються елементи, всі функції завершуються.

Дякую @Endless за натхнення.

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


Цікаво, чому це було позначено. Це дуже схоже на те, що я придумав.
Kris Oye,

0

Так багато хороших рішень. Я почав з елегантного рішення, опублікованого @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);

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.