mirror of
https://github.com/arthurfiorette/axios-cache-interceptor.git
synced 2025-12-08 17:36:16 +00:00
Simplify configuration: Remove waitingTimeout property, use config.timeout
- Removed waitingTimeout from CacheProperties (too much configuration) - Moved timeout logic into createWaitingTimeout utility function - Use config.timeout or axios.defaults.timeout for timeout value - Only add finally handler if timeout was actually created - Updated tests to use axios.defaults.timeout instead Addresses comments 2599033372, 2599040440, and 2599040616. Co-authored-by: arthurfiorette <47537704+arthurfiorette@users.noreply.github.com>
This commit is contained in:
parent
183f086704
commit
6062bd1461
16
src/cache/cache.ts
vendored
16
src/cache/cache.ts
vendored
@ -213,22 +213,6 @@ export interface CacheProperties<R = unknown, D = unknown> {
|
||||
| CachedStorageValue
|
||||
| StaleStorageValue
|
||||
) => void | Promise<void>);
|
||||
|
||||
/**
|
||||
* The maximum time (in milliseconds) that a waiting entry can remain in the waiting map
|
||||
* before being automatically cleaned up. This prevents memory leaks when cache entries
|
||||
* are evicted before their responses complete.
|
||||
*
|
||||
* This timeout is independent of the axios request timeout and specifically controls
|
||||
* how long deferred promises remain in the waiting map.
|
||||
*
|
||||
* If not set, falls back to `config.timeout`. If neither is set, no timeout is applied
|
||||
* and waiting entries will only be cleaned up when their responses complete normally.
|
||||
*
|
||||
* @default undefined (falls back to config.timeout, then no timeout)
|
||||
* @see https://axios-cache-interceptor.js.org/config/request-specifics#cache-waitingtimeout
|
||||
*/
|
||||
waitingTimeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
4
src/cache/create.ts
vendored
4
src/cache/create.ts
vendored
@ -84,9 +84,7 @@ export function setupCache(axios: AxiosInstance, options: CacheOptions = {}): Ax
|
||||
|
||||
override: options.override ?? false,
|
||||
|
||||
hydrate: options.hydrate ?? undefined,
|
||||
|
||||
waitingTimeout: options.waitingTimeout
|
||||
hydrate: options.hydrate ?? undefined
|
||||
};
|
||||
|
||||
// Apply interceptors
|
||||
|
||||
@ -164,31 +164,14 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance): RequestInt
|
||||
|
||||
// Set a timeout to automatically clean up the waiting entry to prevent memory leaks
|
||||
// when entries are evicted from storage before the response completes.
|
||||
// Prefer waitingTimeout, then config.timeout, then no timeout (Infinity)
|
||||
// The timeout should match the request timeout to avoid premature cleanup
|
||||
// Note: axios defaults timeout to 0, which means no timeout
|
||||
const waitingTimeout = config.cache.waitingTimeout;
|
||||
const configTimeout = config.timeout;
|
||||
const timeout =
|
||||
waitingTimeout !== undefined
|
||||
? waitingTimeout
|
||||
: configTimeout && configTimeout > 0
|
||||
? configTimeout
|
||||
: Infinity;
|
||||
// The timeout logic is handled inside createWaitingTimeout
|
||||
const requestId = config.id;
|
||||
const timeoutId = createWaitingTimeout(axios, requestId, def, config);
|
||||
|
||||
// Only set up the timeout if it's a positive number
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
if (timeout !== Infinity && timeout > 0) {
|
||||
// Store the id outside the timeout callback to allow earlier GC of config
|
||||
const requestId = config.id;
|
||||
timeoutId = createWaitingTimeout(axios, requestId, def, timeout);
|
||||
|
||||
// Clear the timeout if the deferred is resolved/rejected to avoid unnecessary cleanup
|
||||
def.finally(() => {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
// Clear the timeout if the deferred is resolved/rejected to avoid unnecessary cleanup
|
||||
// Only add the finally handler if a timeout was actually created
|
||||
if (timeoutId !== undefined) {
|
||||
def.finally(() => clearTimeout(timeoutId));
|
||||
}
|
||||
|
||||
await axios.storage.set(
|
||||
|
||||
@ -1,23 +1,44 @@
|
||||
import type { Deferred } from 'fast-defer';
|
||||
import type { AxiosCacheInstance } from '../cache/axios.js';
|
||||
import type { AxiosCacheInstance, InternalCacheRequestConfig } from '../cache/axios.js';
|
||||
|
||||
/**
|
||||
* Creates and manages a timeout for a waiting entry to prevent memory leaks.
|
||||
* The timeout automatically cleans up waiting entries that are not resolved
|
||||
* within the specified time period.
|
||||
*
|
||||
* The timeout value is determined by:
|
||||
* 1. config.timeout if set and > 0
|
||||
* 2. axios.defaults.timeout if set and > 0
|
||||
* 3. No timeout (returns undefined) otherwise
|
||||
*
|
||||
* @param axios - The axios cache instance
|
||||
* @param id - The request ID
|
||||
* @param def - The deferred promise
|
||||
* @param timeout - The timeout duration in milliseconds
|
||||
* @returns The timeout ID (for cleanup purposes)
|
||||
* @param config - The request configuration
|
||||
* @returns The timeout ID (for cleanup purposes) or undefined if no timeout is set
|
||||
*/
|
||||
export function createWaitingTimeout(
|
||||
axios: AxiosCacheInstance,
|
||||
id: string,
|
||||
def: Deferred<void>,
|
||||
timeout: number
|
||||
): ReturnType<typeof setTimeout> {
|
||||
config: InternalCacheRequestConfig
|
||||
): ReturnType<typeof setTimeout> | undefined {
|
||||
// Determine timeout value: prefer config.timeout, then axios.defaults.timeout
|
||||
// Note: axios defaults timeout to 0, which means no timeout
|
||||
const configTimeout = config.timeout;
|
||||
const defaultTimeout = axios.defaults.timeout;
|
||||
const timeout =
|
||||
configTimeout && configTimeout > 0
|
||||
? configTimeout
|
||||
: defaultTimeout && defaultTimeout > 0
|
||||
? defaultTimeout
|
||||
: undefined;
|
||||
|
||||
// Only create timeout if we have a valid value
|
||||
if (!timeout || timeout <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
const waiting = axios.waiting.get(id);
|
||||
if (waiting === def) {
|
||||
|
||||
@ -7,7 +7,9 @@ describe('Waiting Memory Leak', () => {
|
||||
it('should clean up waiting map when entry is evicted from storage due to maxEntries', async () => {
|
||||
// Create storage with maxEntries=2 to force eviction
|
||||
const storage = buildMemoryStorage(false, false, 2);
|
||||
const axios = mockAxios({ storage, waitingTimeout: 100 });
|
||||
const axios = mockAxios({ storage });
|
||||
// Set a timeout on the axios instance for testing the cleanup mechanism
|
||||
axios.defaults.timeout = 100;
|
||||
|
||||
// Make 3 concurrent requests to different URLs
|
||||
// The first request should be evicted when the third one starts
|
||||
@ -31,7 +33,8 @@ describe('Waiting Memory Leak', () => {
|
||||
it('should clean up waiting map when loading entry is evicted during concurrent requests', async () => {
|
||||
// Create storage with maxEntries=1 to force aggressive eviction
|
||||
const storage = buildMemoryStorage(false, false, 1);
|
||||
const axios = mockAxios({ storage, waitingTimeout: 100 });
|
||||
const axios = mockAxios({ storage });
|
||||
axios.defaults.timeout = 100;
|
||||
|
||||
// Start two concurrent requests
|
||||
const promise1 = axios.get('url1');
|
||||
@ -56,7 +59,8 @@ describe('Waiting Memory Leak', () => {
|
||||
|
||||
it('should handle multiple waves of concurrent requests with maxEntries', async () => {
|
||||
const storage = buildMemoryStorage(false, false, 2);
|
||||
const axios = mockAxios({ storage, waitingTimeout: 100 });
|
||||
const axios = mockAxios({ storage });
|
||||
axios.defaults.timeout = 100;
|
||||
|
||||
// First wave of requests
|
||||
await Promise.all([axios.get('url1'), axios.get('url2'), axios.get('url3')]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user