storyblok-js-client
Version:
Universal JavaScript SDK for Storyblok's API
94 lines (76 loc) • 1.89 kB
text/typescript
import type { ISbThrottle, Queue } from './interfaces';
class AbortError extends Error {
constructor(msg: string) {
super(msg);
this.name = 'AbortError';
}
}
function throttledQueue<T extends (...args: Parameters<T>) => ReturnType<T>>(
fn: T,
limit: number,
interval: number,
): ISbThrottle<T> {
if (!Number.isFinite(limit)) {
throw new TypeError('Expected `limit` to be a finite number');
}
if (!Number.isFinite(interval)) {
throw new TypeError('Expected `interval` to be a finite number');
}
const queue: Queue<Parameters<T>>[] = [];
let timeouts: ReturnType<typeof setTimeout>[] = [];
let activeCount = 0;
let isAborted = false;
const next = async () => {
activeCount++;
const x = queue.shift();
if (x) {
try {
const res = await fn(...x.args);
x.resolve(res);
}
catch (error) {
x.reject(error);
}
}
const id = setTimeout(() => {
activeCount--;
if (queue.length > 0) {
next();
}
timeouts = timeouts.filter(currentId => currentId !== id);
}, interval);
if (!timeouts.includes(id)) {
timeouts.push(id);
}
};
const throttled: ISbThrottle<T> = (...args) => {
if (isAborted) {
return Promise.reject(
new Error(
'Throttled function is already aborted and not accepting new promises',
),
);
}
return new Promise((resolve, reject) => {
queue.push({
resolve,
reject,
args,
});
if (activeCount < limit) {
next();
}
});
};
throttled.abort = () => {
isAborted = true;
timeouts.forEach(clearTimeout);
timeouts = [];
queue.forEach(x =>
x.reject(() => new AbortError('Throttle function aborted')),
);
queue.length = 0;
};
return throttled;
}
export default throttledQueue;