|  | 
|  | 1 | +/** | 
|  | 2 | + * This file is jury-rigged.  It's the entire transitive code of 'p-limit'. | 
|  | 3 | + *  | 
|  | 4 | + * - p-limit does not support CJS. | 
|  | 5 | + * - esm does not support p-limit. | 
|  | 6 | + * - This could be resolved in future by upgrading the entire project to use built-in ESM | 
|  | 7 | + *   in new versions of node.  However, all the imports would need to be changed.  It would | 
|  | 8 | + *   be a major project. | 
|  | 9 | + */ | 
|  | 10 | + | 
|  | 11 | + | 
|  | 12 | + | 
|  | 13 | + | 
|  | 14 | + | 
|  | 15 | + | 
|  | 16 | + | 
|  | 17 | + | 
|  | 18 | +/* | 
|  | 19 | +How it works: | 
|  | 20 | +`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value. | 
|  | 21 | +*/ | 
|  | 22 | + | 
|  | 23 | +class Node { | 
|  | 24 | +	value; | 
|  | 25 | +	next; | 
|  | 26 | + | 
|  | 27 | +	constructor(value) { | 
|  | 28 | +		this.value = value; | 
|  | 29 | +	} | 
|  | 30 | +} | 
|  | 31 | + | 
|  | 32 | +class Queue { | 
|  | 33 | +	#head; | 
|  | 34 | +	#tail; | 
|  | 35 | +	#size; | 
|  | 36 | + | 
|  | 37 | +	constructor() { | 
|  | 38 | +		this.clear(); | 
|  | 39 | +	} | 
|  | 40 | + | 
|  | 41 | +	enqueue(value) { | 
|  | 42 | +		const node = new Node(value); | 
|  | 43 | + | 
|  | 44 | +		if (this.#head) { | 
|  | 45 | +			this.#tail.next = node; | 
|  | 46 | +			this.#tail = node; | 
|  | 47 | +		} else { | 
|  | 48 | +			this.#head = node; | 
|  | 49 | +			this.#tail = node; | 
|  | 50 | +		} | 
|  | 51 | + | 
|  | 52 | +		this.#size++; | 
|  | 53 | +	} | 
|  | 54 | + | 
|  | 55 | +	dequeue() { | 
|  | 56 | +		const current = this.#head; | 
|  | 57 | +		if (!current) { | 
|  | 58 | +			return; | 
|  | 59 | +		} | 
|  | 60 | + | 
|  | 61 | +		this.#head = this.#head.next; | 
|  | 62 | +		this.#size--; | 
|  | 63 | +		return current.value; | 
|  | 64 | +	} | 
|  | 65 | + | 
|  | 66 | +	clear() { | 
|  | 67 | +		this.#head = undefined; | 
|  | 68 | +		this.#tail = undefined; | 
|  | 69 | +		this.#size = 0; | 
|  | 70 | +	} | 
|  | 71 | + | 
|  | 72 | +	get size() { | 
|  | 73 | +		return this.#size; | 
|  | 74 | +	} | 
|  | 75 | + | 
|  | 76 | +	* [Symbol.iterator]() { | 
|  | 77 | +		let current = this.#head; | 
|  | 78 | + | 
|  | 79 | +		while (current) { | 
|  | 80 | +			yield current.value; | 
|  | 81 | +			current = current.next; | 
|  | 82 | +		} | 
|  | 83 | +	} | 
|  | 84 | +} | 
|  | 85 | + | 
|  | 86 | +export default function pLimit(concurrency) { | 
|  | 87 | +	if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) { | 
|  | 88 | +		throw new TypeError('Expected `concurrency` to be a number from 1 and up'); | 
|  | 89 | +	} | 
|  | 90 | + | 
|  | 91 | +	const queue = new Queue(); | 
|  | 92 | +	let activeCount = 0; | 
|  | 93 | + | 
|  | 94 | +	const next = () => { | 
|  | 95 | +		activeCount--; | 
|  | 96 | + | 
|  | 97 | +		if (queue.size > 0) { | 
|  | 98 | +			queue.dequeue()(); | 
|  | 99 | +		} | 
|  | 100 | +	}; | 
|  | 101 | + | 
|  | 102 | +	const run = async (fn, resolve, args) => { | 
|  | 103 | +		activeCount++; | 
|  | 104 | + | 
|  | 105 | +		const result = (async () => fn(...args))(); | 
|  | 106 | + | 
|  | 107 | +		resolve(result); | 
|  | 108 | + | 
|  | 109 | +		try { | 
|  | 110 | +			await result; | 
|  | 111 | +		} catch {} | 
|  | 112 | + | 
|  | 113 | +		next(); | 
|  | 114 | +	}; | 
|  | 115 | + | 
|  | 116 | +	const enqueue = (fn, resolve, args) => { | 
|  | 117 | +		queue.enqueue(run.bind(undefined, fn, resolve, args)); | 
|  | 118 | + | 
|  | 119 | +		(async () => { | 
|  | 120 | +			// This function needs to wait until the next microtask before comparing | 
|  | 121 | +			// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously | 
|  | 122 | +			// when the run function is dequeued and called. The comparison in the if-statement | 
|  | 123 | +			// needs to happen asynchronously as well to get an up-to-date value for `activeCount`. | 
|  | 124 | +			await Promise.resolve(); | 
|  | 125 | + | 
|  | 126 | +			if (activeCount < concurrency && queue.size > 0) { | 
|  | 127 | +				queue.dequeue()(); | 
|  | 128 | +			} | 
|  | 129 | +		})(); | 
|  | 130 | +	}; | 
|  | 131 | + | 
|  | 132 | +	const generator = (fn, ...args) => new Promise(resolve => { | 
|  | 133 | +		enqueue(fn, resolve, args); | 
|  | 134 | +	}); | 
|  | 135 | + | 
|  | 136 | +	Object.defineProperties(generator, { | 
|  | 137 | +		activeCount: { | 
|  | 138 | +			get: () => activeCount, | 
|  | 139 | +		}, | 
|  | 140 | +		pendingCount: { | 
|  | 141 | +			get: () => queue.size, | 
|  | 142 | +		}, | 
|  | 143 | +		clearQueue: { | 
|  | 144 | +			value: () => { | 
|  | 145 | +				queue.clear(); | 
|  | 146 | +			}, | 
|  | 147 | +		}, | 
|  | 148 | +	}); | 
|  | 149 | + | 
|  | 150 | +	return generator; | 
|  | 151 | +} | 
0 commit comments