UNPKG

p-limit

Version:

Run multiple promise-returning & async functions with limited concurrency

107 lines (88 loc) 2.82 kB
import Queue from 'yocto-queue'; export default function pLimit(concurrency) { validateConcurrency(concurrency); const queue = new Queue(); let activeCount = 0; const resumeNext = () => { // Process the next queued function if we're under the concurrency limit if (activeCount < concurrency && queue.size > 0) { activeCount++; queue.dequeue()(); } }; const next = () => { activeCount--; resumeNext(); }; const run = async (function_, resolve, arguments_) => { // Execute the function and capture the result promise const result = (async () => function_(...arguments_))(); // Resolve immediately with the promise (don't wait for completion) resolve(result); // Wait for the function to complete (success or failure) // We catch errors here to prevent unhandled rejections, // but the original promise rejection is preserved for the caller try { await result; } catch {} // Decrement active count and process next queued function next(); }; const enqueue = (function_, resolve, arguments_) => { // Queue the internal resolve function instead of the run function // to preserve the asynchronous execution context. new Promise(internalResolve => { // eslint-disable-line promise/param-names queue.enqueue(internalResolve); }).then(run.bind(undefined, function_, resolve, arguments_)); // eslint-disable-line promise/prefer-await-to-then // Start processing immediately if we haven't reached the concurrency limit if (activeCount < concurrency) { resumeNext(); } }; const generator = (function_, ...arguments_) => new Promise(resolve => { enqueue(function_, resolve, arguments_); }); Object.defineProperties(generator, { activeCount: { get: () => activeCount, }, pendingCount: { get: () => queue.size, }, clearQueue: { value() { queue.clear(); }, }, concurrency: { get: () => concurrency, set(newConcurrency) { validateConcurrency(newConcurrency); concurrency = newConcurrency; queueMicrotask(() => { // eslint-disable-next-line no-unmodified-loop-condition while (activeCount < concurrency && queue.size > 0) { resumeNext(); } }); }, }, map: { async value(array, function_) { const promises = array.map((value, index) => this(function_, value, index)); return Promise.all(promises); }, }, }); return generator; } export function limitFunction(function_, options) { const {concurrency} = options; const limit = pLimit(concurrency); return (...arguments_) => limit(() => function_(...arguments_)); } function validateConcurrency(concurrency) { if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) { throw new TypeError('Expected `concurrency` to be a number from 1 and up'); } }