@tanstack/query-core
Version:
The framework agnostic core that powers TanStack Query
1 lines • 11.6 kB
Source Map (JSON)
{"version":3,"file":"timeoutManager.cjs","names":[],"sources":["../../src/timeoutManager.ts"],"sourcesContent":["/**\n * {@link TimeoutManager} does not support passing arguments to the callback.\n *\n * `(_: void)` is the argument type inferred by TypeScript's default typings for\n * `setTimeout(cb, number)`.\n * If we don't accept a single void argument, then\n * `new Promise(resolve => timeoutManager.setTimeout(resolve, N))` is a type error.\n */\nexport type TimeoutCallback = (_: void) => void\n\n/**\n * Wrapping `setTimeout` is awkward from a typing perspective because platform\n * typings may extend the return type of `setTimeout`. For example, NodeJS\n * typings add `NodeJS.Timeout`; but a non-default `timeoutManager` may not be\n * able to return such a type.\n */\nexport type ManagedTimerId = number | { [Symbol.toPrimitive]: () => number }\n\n/**\n * Backend for timer functions.\n *\n * Timers are performance-sensitive: short-lived timers (delays under a few seconds) tend to be\n * latency-sensitive, while long-lived ones may benefit more from coalescing — batching timers\n * with similar deadlines together — which the default provider (backed by the platform's global\n * `setTimeout`/`setInterval`) does not do. A custom provider can implement coalescing, and can\n * also support delays longer than the ~24-day maximum of the global `setTimeout`.\n */\nexport type TimeoutProvider<TTimerId extends ManagedTimerId = ManagedTimerId> =\n {\n /**\n * Schedules `callback` to run once after `delay` milliseconds, like the global `setTimeout`.\n */\n readonly setTimeout: (callback: TimeoutCallback, delay: number) => TTimerId\n /**\n * Cancels a timeout scheduled with `setTimeout`.\n */\n readonly clearTimeout: (timeoutId: TTimerId | undefined) => void\n\n /**\n * Schedules `callback` to run every `delay` milliseconds, like the global `setInterval`.\n */\n readonly setInterval: (callback: TimeoutCallback, delay: number) => TTimerId\n /**\n * Cancels an interval scheduled with `setInterval`.\n */\n readonly clearInterval: (intervalId: TTimerId | undefined) => void\n }\n\ntype SystemTimerId = ReturnType<typeof setTimeout>\n\nexport const defaultTimeoutProvider: TimeoutProvider = {\n // We need the wrapper function syntax below instead of direct references to\n // global setTimeout etc.\n //\n // BAD: `setTimeout: setTimeout`\n // GOOD: `setTimeout: (cb, delay) => setTimeout(cb, delay)`\n //\n // If we use direct references here, then anything that wants to spy on or\n // replace the global setTimeout (like tests) won't work since we'll already\n // have a hard reference to the original implementation at the time when this\n // file was imported.\n setTimeout: (callback, delay) => setTimeout(callback, delay),\n clearTimeout: (timeoutId) =>\n clearTimeout(timeoutId as SystemTimerId | undefined),\n\n setInterval: (callback, delay) => setInterval(callback, delay),\n clearInterval: (intervalId) =>\n clearInterval(intervalId as SystemTimerId | undefined),\n}\n\n/**\n * Allows customization of how timeouts are created.\n *\n * `@tanstack/query-core` makes liberal use of timeouts to implement `staleTime`\n * and `gcTime`. The default TimeoutManager provider uses the platform's global\n * `setTimeout` implementation, which is known to have scalability issues with\n * thousands of timeouts on the event loop.\n *\n * If you hit this limitation, consider providing a custom TimeoutProvider that\n * coalesces timeouts.\n */\nexport class TimeoutManager implements Omit<TimeoutProvider, 'name'> {\n // We cannot have TimeoutManager<T> as we must instantiate it with a concrete\n // type at app boot; and if we leave that type, then any new timer provider\n // would need to support the default provider's concrete timer ID, which is\n // infeasible across environments.\n //\n // We settle for type safety for the TimeoutProvider type, and accept that\n // this class is unsafe internally to allow for extension.\n #provider: TimeoutProvider<any> = defaultTimeoutProvider\n #providerCalled = false\n\n /**\n * `setTimeoutProvider` can be used to set a custom implementation of the\n * `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval` functions,\n * called a `TimeoutProvider`.\n *\n * This may be useful if you notice event loop performance issues with\n * thousands of queries. A custom TimeoutProvider could also support timer\n * delays longer than the global `setTimeout` maximum delay value of about\n * 24 days.\n *\n * It is important to call `setTimeoutProvider` before creating a\n * QueryClient or queries, so that the same provider is used consistently\n * for all timers in the application, since different TimeoutProviders\n * cannot cancel each others' timers.\n * @param provider - The `TimeoutProvider` to use for all timers from now on.\n * @example\n * ```ts\n * import { timeoutManager, QueryClient } from '@tanstack/query-core'\n * import { CustomTimeoutProvider } from './CustomTimeoutProvider'\n *\n * timeoutManager.setTimeoutProvider(new CustomTimeoutProvider())\n *\n * export const queryClient = new QueryClient()\n * ```\n */\n setTimeoutProvider<TTimerId extends ManagedTimerId>(\n provider: TimeoutProvider<TTimerId>,\n ): void {\n if (process.env.NODE_ENV !== 'production') {\n if (this.#providerCalled && provider !== this.#provider) {\n // After changing providers, `clearTimeout` will not work as expected for\n // timeouts from the previous provider.\n //\n // Since they may allocate the same timeout ID, clearTimeout may cancel an\n // arbitrary different timeout, or unexpected no-op.\n //\n // We could protect against this by mixing the timeout ID bits\n // deterministically with some per-provider bits.\n //\n // We could internally queue `setTimeout` calls to `TimeoutManager` until\n // some API call to set the initial provider.\n console.error(\n `[timeoutManager]: Switching provider after calls to previous provider might result in unexpected behavior.`,\n { previous: this.#provider, provider },\n )\n }\n }\n\n this.#provider = provider\n if (process.env.NODE_ENV !== 'production') {\n this.#providerCalled = false\n }\n }\n\n /**\n * `setTimeout` schedules a callback to run after approximately `delay`\n * milliseconds, like the global `setTimeout` function. The callback can be\n * canceled with `clearTimeout`.\n *\n * It returns a timer ID, which may be a number or an object that can be\n * coerced to a number via `Symbol.toPrimitive`.\n * @param callback - The function to call when the timeout elapses.\n * @param delay - The time to wait before calling `callback`, in milliseconds.\n * @returns The timer ID, to pass to {@link TimeoutManager#clearTimeout}.\n * @example\n * ```ts\n * import { timeoutManager } from '@tanstack/query-core'\n *\n * const timeoutId = timeoutManager.setTimeout(\n * () => console.log('ran at:', new Date()),\n * 1000,\n * )\n *\n * const timeoutIdNumber: number = Number(timeoutId)\n * ```\n */\n setTimeout(callback: TimeoutCallback, delay: number): ManagedTimerId {\n if (process.env.NODE_ENV !== 'production') {\n this.#providerCalled = true\n }\n return this.#provider.setTimeout(callback, delay)\n }\n\n /**\n * `clearTimeout` cancels a timeout callback scheduled with `setTimeout`,\n * like the global `clearTimeout` function. It should be called with a\n * timer ID returned by `setTimeout`.\n * @param timeoutId - The timer ID returned by `setTimeout`, or `undefined`.\n * @example\n * ```ts\n * import { timeoutManager } from '@tanstack/query-core'\n *\n * const timeoutId = timeoutManager.setTimeout(\n * () => console.log('ran at:', new Date()),\n * 1000,\n * )\n *\n * timeoutManager.clearTimeout(timeoutId)\n * ```\n */\n clearTimeout(timeoutId: ManagedTimerId | undefined): void {\n this.#provider.clearTimeout(timeoutId)\n }\n\n /**\n * `setInterval` schedules a callback to be called approximately every\n * `delay` milliseconds, like the global `setInterval` function.\n *\n * Like `setTimeout`, it returns a timer ID, which may be a number or an\n * object that can be coerced to a number via `Symbol.toPrimitive`.\n * @param callback - The function to call on every interval.\n * @param delay - The time between calls, in milliseconds.\n * @returns The timer ID, to pass to {@link TimeoutManager#clearInterval}.\n * @example\n * ```ts\n * import { timeoutManager } from '@tanstack/query-core'\n *\n * const intervalId = timeoutManager.setInterval(\n * () => console.log('ran at:', new Date()),\n * 1000,\n * )\n * ```\n */\n setInterval(callback: TimeoutCallback, delay: number): ManagedTimerId {\n if (process.env.NODE_ENV !== 'production') {\n this.#providerCalled = true\n }\n return this.#provider.setInterval(callback, delay)\n }\n\n /**\n * `clearInterval` can be used to cancel an interval, like the global\n * `clearInterval` function. It should be called with an interval ID\n * returned by `setInterval`.\n * @param intervalId - The timer ID returned by `setInterval`, or `undefined`.\n * @example\n * ```ts\n * import { timeoutManager } from '@tanstack/query-core'\n *\n * const intervalId = timeoutManager.setInterval(\n * () => console.log('ran at:', new Date()),\n * 1000,\n * )\n *\n * timeoutManager.clearInterval(intervalId)\n * ```\n */\n clearInterval(intervalId: ManagedTimerId | undefined): void {\n this.#provider.clearInterval(intervalId)\n }\n}\n\n/**\n * Singleton instance of {@link TimeoutManager}, used throughout TanStack Query to schedule and cancel timers.\n */\nexport const timeoutManager = new TimeoutManager()\n\n/**\n * In many cases code wants to delay to the next event loop tick; this is not\n * mediated by {@link timeoutManager}.\n *\n * This function is provided to make auditing the `tanstack/query-core` for\n * incorrect use of system `setTimeout` easier.\n * @param callback - The function to call on the next event loop tick.\n */\nexport function systemSetTimeoutZero(callback: TimeoutCallback): void {\n setTimeout(callback, 0)\n}\n"],"mappings":";;;AAkDA,MAAa,yBAA0C;CAWrD,aAAa,UAAU,UAAU,WAAW,UAAU,KAAK;CAC3D,eAAe,cACb,aAAa,SAAsC;CAErD,cAAc,UAAU,UAAU,YAAY,UAAU,KAAK;CAC7D,gBAAgB,eACd,cAAc,UAAuC;AACzD;;;;;;;;;;;;;;AAaA,IAAa,iBAAb,MAAqE;;EAQjC,8BAAA,2BAAA,MAAA,WAAA,sBAAA;EAChB,8BAAA,2BAAA,MAAA,iBAAA,KAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BlB,mBACE,UACM;EACN,IAAI,QAAQ,IAAI,aAAa,cACvB;OAAA,8BAAA,uBAAA,iBAAA,IAAA,KAAwB,aAAA,8BAAA,uBAAA,WAAa,IAAA,GAYvC,QAAQ,MACN,8GACA;IAAE,UAAA,8BAAA,uBAAA,WAAU,IAAA;IAAgB;GAAS,CACvC;EAAA;EAIJ,8BAAA,uBAAA,WAAA,MAAiB,QAAA;EACjB,IAAI,QAAQ,IAAI,aAAa,cAC3B,8BAAA,uBAAA,iBAAA,MAAuB,KAAA;CAE3B;;;;;;;;;;;;;;;;;;;;;;;CAwBA,WAAW,UAA2B,OAA+B;EACnE,IAAI,QAAQ,IAAI,aAAa,cAC3B,8BAAA,uBAAA,iBAAA,MAAuB,IAAA;EAEzB,OAAA,8BAAA,uBAAA,WAAO,IAAA,CAAA,CAAe,WAAW,UAAU,KAAK;CAClD;;;;;;;;;;;;;;;;;;CAmBA,aAAa,WAA6C;EACxD,8BAAA,uBAAA,WAAA,IAAA,CAAA,CAAe,aAAa,SAAS;CACvC;;;;;;;;;;;;;;;;;;;;CAqBA,YAAY,UAA2B,OAA+B;EACpE,IAAI,QAAQ,IAAI,aAAa,cAC3B,8BAAA,uBAAA,iBAAA,MAAuB,IAAA;EAEzB,OAAA,8BAAA,uBAAA,WAAO,IAAA,CAAA,CAAe,YAAY,UAAU,KAAK;CACnD;;;;;;;;;;;;;;;;;;CAmBA,cAAc,YAA8C;EAC1D,8BAAA,uBAAA,WAAA,IAAA,CAAA,CAAe,cAAc,UAAU;CACzC;AACF;;;;AAKA,MAAa,iBAAiB,IAAI,eAAe;;;;;;;;;AAUjD,SAAgB,qBAAqB,UAAiC;CACpE,WAAW,UAAU,CAAC;AACxB"}