@graphql-tools/load
Version:
A set of utils for faster development of GraphQL tools
109 lines (108 loc) • 3.4 kB
JavaScript
function isThenable(value) {
return value != null && typeof value.then === 'function';
}
export function useQueue(options) {
const tasks = [];
const concurrency = options?.concurrency;
return {
add(fn) {
tasks.push(fn);
},
runAll() {
const total = tasks.length;
if (total === 0) {
return;
}
const limit = concurrency ?? total;
let index = 0;
let activeCount = 0;
let settledCount = 0;
let failed = false;
let resolveDone;
let rejectDone;
let donePromise;
const ensureDonePromise = () => {
if (!donePromise) {
({
promise: donePromise,
resolve: resolveDone,
reject: rejectDone,
} = Promise.withResolvers());
}
return donePromise;
};
const onSettle = () => {
settledCount++;
if (failed) {
return;
}
if (settledCount === total) {
resolveDone?.();
return;
}
pump();
};
const pump = () => {
while (!failed && activeCount < limit && index < total) {
const task = tasks[index++];
activeCount++;
let result;
try {
result = task();
}
catch (error) {
failed = true;
activeCount--;
// If callers are already awaiting the completion gate, reject it.
// Otherwise keep the sync failure synchronous.
if (rejectDone) {
rejectDone(error);
return;
}
throw error;
}
if (isThenable(result)) {
// Only async work needs a completion gate; sync results stay sync.
ensureDonePromise();
result.then(() => {
activeCount--;
onSettle();
}, error => {
failed = true;
activeCount--;
rejectDone(error);
});
}
else {
activeCount--;
settledCount++;
}
}
if (!failed && settledCount === total) {
resolveDone?.();
}
};
pump();
if (failed) {
return donePromise;
}
if (settledCount === total) {
return;
}
return ensureDonePromise();
},
};
}
export function useSyncQueue() {
const queue = [];
return {
add(fn) {
queue.push(fn);
},
runAll() {
for (const fn of queue) {
fn();
}
},
};
}