contentful-sdk-core
Version:
Core modules for the Contentful JS SDKs
1 lines • 7.96 kB
Source Map (JSON)
{"version":3,"file":"pThrottle.js","sources":["../../src/pThrottle.ts"],"sourcesContent":["export class AbortError extends Error {\n readonly name = 'AbortError' as const\n\n constructor() {\n super('Throttled function aborted')\n }\n}\n\ntype AnyFunction = (...arguments_: readonly any[]) => unknown\n\nexport type ThrottledFunction<F extends AnyFunction> = F & {\n /**\n * Whether future function calls should be throttled or count towards throttling thresholds.\n *\n * @default true\n */\n isEnabled: boolean\n\n /**\n * The number of queued items waiting to be executed.\n */\n readonly queueSize: number\n\n /**\n * Abort pending executions. All unresolved promises are rejected with a `pThrottle.AbortError` error.\n */\n abort(): void\n}\n\nexport type Options = {\n /**\n * The maximum number of calls within an `interval`.\n */\n readonly limit: number\n\n /**\n * The timespan for `limit` in milliseconds.\n */\n readonly interval: number\n\n /**\n * Use a strict, more resource intensive, throttling algorithm. The default algorithm uses a windowed approach that will work correctly in most cases, limiting the total number of calls at the specified limit per interval window. The strict algorithm throttles each call individually, ensuring the limit is not exceeded for any interval.\n *\n * @default false\n */\n readonly strict?: boolean\n\n /**\n * Get notified when function calls are delayed due to exceeding the `limit` of allowed calls within the given `interval`.\n *\n * Can be useful for monitoring the throttling efficiency.\n *\n * @example\n * ```\n * import pThrottle from './PThrottle';\n *\n * const throttle = pThrottle({\n * limit: 2,\n * interval: 1000,\n * onDelay: () => {\n * console.log('Reached interval limit, call is delayed');\n * },\n * });\n *\n * const throttled = throttle(() => {\n * console.log('Executing...');\n * });\n *\n * await throttled();\n * await throttled();\n * await throttled();\n * //=> Executing...\n * //=> Executing...\n * //=> Reached interval limit, call is delayed\n * //=> Executing...\n * ```\n */\n readonly onDelay?: () => void\n}\n\n/**\n * Throttle promise-returning/async/normal functions.\n *\n * It rate-limits function calls without discarding them, making it ideal for external API interactions where avoiding call loss is crucial.\n *\n * @returns A throttle function.\n *\n * Both the `limit` and `interval` options must be specified.\n *\n * @example\n * ```\n * import pThrottle from './PThrottle';\n *\n * const now = Date.now();\n *\n * const throttle = pThrottle({\n * limit: 2,\n * interval: 1000\n * });\n *\n * const throttled = throttle(async index => {\n * const secDiff = ((Date.now() - now) / 1000).toFixed();\n * return `${index}: ${secDiff}s`;\n * });\n *\n * for (let index = 1; index <= 6; index++) {\n * (async () => {\n * console.log(await throttled(index));\n * })();\n * }\n * //=> 1: 0s\n * //=> 2: 0s\n * //=> 3: 1s\n * //=> 4: 1s\n * //=> 5: 2s\n * //=> 6: 2s\n * ```\n */\nexport default function pThrottle({ limit, interval, strict, onDelay }: Options) {\n if (!Number.isFinite(limit)) {\n throw new TypeError('Expected `limit` to be a finite number')\n }\n\n if (!Number.isFinite(interval)) {\n throw new TypeError('Expected `interval` to be a finite number')\n }\n\n const queue = new Map<NodeJS.Timeout, (error: AbortError) => void>()\n\n let currentTick = 0\n let activeCount = 0\n\n function windowedDelay() {\n const now = Date.now()\n\n if (now - currentTick > interval) {\n activeCount = 1\n currentTick = now\n return 0\n }\n\n if (activeCount < limit) {\n activeCount++\n } else {\n currentTick += interval\n activeCount = 1\n }\n\n return currentTick - now\n }\n\n const strictTicks: number[] = []\n\n function strictDelay() {\n const now = Date.now()\n\n // Clear the queue if there's a significant delay since the last execution\n if (strictTicks.length > 0 && now - strictTicks.at(-1)! > interval) {\n strictTicks.length = 0\n }\n\n // If the queue is not full, add the current time and execute immediately\n if (strictTicks.length < limit) {\n strictTicks.push(now)\n return 0\n }\n\n // Calculate the next execution time based on the first item in the queue\n const nextExecutionTime = strictTicks[0] + interval\n\n // Shift the queue and add the new execution time\n strictTicks.shift()\n strictTicks.push(nextExecutionTime)\n\n // Calculate the delay for the current execution\n return Math.max(0, nextExecutionTime - now)\n }\n\n const getDelay = strict ? strictDelay : windowedDelay\n\n return function <F extends AnyFunction>(function_: F): ThrottledFunction<F> {\n const throttled = function (this: any, ...arguments_: any[]) {\n if (!throttled.isEnabled) {\n return (async () => function_.apply(this, arguments_))()\n }\n\n let timeoutId: NodeJS.Timeout\n return new Promise((resolve, reject) => {\n const execute = () => {\n resolve(function_.apply(this, arguments_))\n queue.delete(timeoutId)\n }\n\n const delay = getDelay()\n if (delay > 0) {\n timeoutId = setTimeout(execute, delay)\n queue.set(timeoutId, reject)\n onDelay?.()\n } else {\n execute()\n }\n })\n } as ThrottledFunction<F>\n\n throttled.abort = () => {\n for (const timeout of queue.keys()) {\n clearTimeout(timeout)\n queue.get(timeout)!(new AbortError())\n }\n\n queue.clear()\n strictTicks.splice(0, strictTicks.length)\n }\n\n throttled.isEnabled = true\n\n Object.defineProperty(throttled, 'queueSize', {\n get() {\n return queue.size\n },\n })\n\n return throttled\n }\n}\n"],"names":[],"mappings":"AAAM,MAAO,UAAW,SAAQ,KAAK,CAAA;IAC1B,IAAI,GAAG,YAAqB;AAErC,IAAA,WAAA,GAAA;QACE,KAAK,CAAC,4BAA4B,CAAC;IACrC;AACD;AA0ED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCG;AACW,SAAU,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAW,EAAA;IAC7E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;AAC3B,QAAA,MAAM,IAAI,SAAS,CAAC,wCAAwC,CAAC;IAC/D;IAEA,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;AAC9B,QAAA,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC;IAClE;AAEA,IAAA,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+C;IAEpE,IAAI,WAAW,GAAG,CAAC;IACnB,IAAI,WAAW,GAAG,CAAC;AAEnB,IAAA,SAAS,aAAa,GAAA;AACpB,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;AAEtB,QAAA,IAAI,GAAG,GAAG,WAAW,GAAG,QAAQ,EAAE;YAChC,WAAW,GAAG,CAAC;YACf,WAAW,GAAG,GAAG;AACjB,YAAA,OAAO,CAAC;QACV;AAEA,QAAA,IAAI,WAAW,GAAG,KAAK,EAAE;AACvB,YAAA,WAAW,EAAE;QACf;aAAO;YACL,WAAW,IAAI,QAAQ;YACvB,WAAW,GAAG,CAAC;QACjB;QAEA,OAAO,WAAW,GAAG,GAAG;IAC1B;IA6BA,MAAM,QAAQ,GAA0B,aAAa;AAErD,IAAA,OAAO,UAAiC,SAAY,EAAA;AAClD,QAAA,MAAM,SAAS,GAAG,UAAqB,GAAG,UAAiB,EAAA;AACzD,YAAA,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE;AACxB,gBAAA,OAAO,CAAC,YAAY,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG;YAC1D;AAEA,YAAA,IAAI,SAAyB;YAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,KAAI;gBACrC,MAAM,OAAO,GAAG,MAAK;oBACnB,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAC1C,oBAAA,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;AACzB,gBAAA,CAAC;AAED,gBAAA,MAAM,KAAK,GAAG,QAAQ,EAAE;AACxB,gBAAA,IAAI,KAAK,GAAG,CAAC,EAAE;AACb,oBAAA,SAAS,GAAG,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC;AACtC,oBAAA,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC;oBAC5B,OAAO,IAAI;gBACb;qBAAO;AACL,oBAAA,OAAO,EAAE;gBACX;AACF,YAAA,CAAC,CAAC;AACJ,QAAA,CAAyB;AAEzB,QAAA,SAAS,CAAC,KAAK,GAAG,MAAK;YACrB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE;gBAClC,YAAY,CAAC,OAAO,CAAC;gBACrB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAE,CAAC,IAAI,UAAU,EAAE,CAAC;YACvC;YAEA,KAAK,CAAC,KAAK,EAAE;AAEf,QAAA,CAAC;AAED,QAAA,SAAS,CAAC,SAAS,GAAG,IAAI;AAE1B,QAAA,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE;YAC5C,GAAG,GAAA;gBACD,OAAO,KAAK,CAAC,IAAI;YACnB,CAAC;AACF,SAAA,CAAC;AAEF,QAAA,OAAO,SAAS;AAClB,IAAA,CAAC;AACH;;;;"}