Skip to content

Instantly share code, notes, and snippets.

@mg901
Last active June 10, 2026 16:09
Show Gist options
  • Select an option

  • Save mg901/718dbd1f28cc684bee6d57d759542eb3 to your computer and use it in GitHub Desktop.

Select an option

Save mg901/718dbd1f28cc684bee6d57d759542eb3 to your computer and use it in GitHub Desktop.
class Semaphore {
#queue = [];
#slots;
constructor(limit) {
this.#slots = Math.max(1, limit);
}
acquire() {
const { promise, resolve } = Promise.withResolvers();
if (this.#slots > 0) {
this.#slots -= 1;
resolve();
return promise;
}
this.#queue.push(resolve);
return promise;
}
release() {
if (this.#queue.length > 0) {
const next = this.#queue.shift();
next();
} else {
this.#slots += 1;
}
}
}
export default function mapAsyncLimit(iterable, callbackFn, size = Infinity) {
const { promise, resolve, reject } = Promise.withResolvers();
const semaphore = new Semaphore(size);
const result = [];
let index = 0;
let remaining = 0;
let rejected = false;
try {
for (const item of iterable) {
runTask(item);
}
} catch (error) {
reject(error);
return promise;
}
if (remaining === 0) {
resolve(result);
}
return promise;
function runTask(item) {
remaining += 1;
const thisIndex = index;
index += 1;
semaphore
.acquire()
.then(() => {
if (rejected) return;
return callbackFn(item);
})
.then((value) => {
if (rejected) return;
remaining -= 1;
result[thisIndex] = value;
if (remaining === 0) {
resolve(result);
}
})
.catch((error) => {
if (rejected) return;
rejected = true;
reject(error);
})
.finally(() => {
semaphore.release();
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment