contentful-sdk-core
Version:
Core modules for the Contentful JS SDKs
113 lines (111 loc) • 3.11 kB
JavaScript
class AbortError extends Error {
name = 'AbortError';
constructor() {
super('Throttled function aborted');
}
}
/**
* Throttle promise-returning/async/normal functions.
*
* It rate-limits function calls without discarding them, making it ideal for external API interactions where avoiding call loss is crucial.
*
* @returns A throttle function.
*
* Both the `limit` and `interval` options must be specified.
*
* @example
* ```
* import pThrottle from './PThrottle';
*
* const now = Date.now();
*
* const throttle = pThrottle({
* limit: 2,
* interval: 1000
* });
*
* const throttled = throttle(async index => {
* const secDiff = ((Date.now() - now) / 1000).toFixed();
* return `${index}: ${secDiff}s`;
* });
*
* for (let index = 1; index <= 6; index++) {
* (async () => {
* console.log(await throttled(index));
* })();
* }
* //=> 1: 0s
* //=> 2: 0s
* //=> 3: 1s
* //=> 4: 1s
* //=> 5: 2s
* //=> 6: 2s
* ```
*/
function pThrottle({ limit, interval, strict, onDelay }) {
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 = new Map();
let currentTick = 0;
let activeCount = 0;
function windowedDelay() {
const now = Date.now();
if (now - currentTick > interval) {
activeCount = 1;
currentTick = now;
return 0;
}
if (activeCount < limit) {
activeCount++;
}
else {
currentTick += interval;
activeCount = 1;
}
return currentTick - now;
}
const getDelay = windowedDelay;
return function (function_) {
const throttled = function (...arguments_) {
if (!throttled.isEnabled) {
return (async () => function_.apply(this, arguments_))();
}
let timeoutId;
return new Promise((resolve, reject) => {
const execute = () => {
resolve(function_.apply(this, arguments_));
queue.delete(timeoutId);
};
const delay = getDelay();
if (delay > 0) {
timeoutId = setTimeout(execute, delay);
queue.set(timeoutId, reject);
onDelay?.();
}
else {
execute();
}
});
};
throttled.abort = () => {
for (const timeout of queue.keys()) {
clearTimeout(timeout);
queue.get(timeout)(new AbortError());
}
queue.clear();
};
throttled.isEnabled = true;
Object.defineProperty(throttled, 'queueSize', {
get() {
return queue.size;
},
});
return throttled;
};
}
export { AbortError, pThrottle as default };
//# sourceMappingURL=pThrottle.js.map