UNPKG

idmp

Version:

A lightweight TypeScript library for deduplicating and caching async function calls with automatic retries, designed for idempotent network requests in React and Node.js.

247 lines (246 loc) 4.73 kB
/*! idmp v3.4.4 | (c) github/haozi | MIT */ var __pow = Math.pow; const DEFAULT_MAX_AGE = 3e3; const _7days = 6048e5; const noop = () => { }; const UNDEFINED = void 0; const $timeout = setTimeout; const getMax = (a, b) => { return a > b ? a : b; }; const getMin = (a, b) => { return a < b ? a : b; }; const getRange = (maxAge) => { if (maxAge < 0) return 0; if (maxAge > _7days) return _7days; return maxAge; }; let _globalStore = {}; const getOptions = (options) => { const { maxRetry = 30, maxAge: paramMaxAge = DEFAULT_MAX_AGE, minRetryDelay = 50, maxRetryDelay = 5e3, onBeforeRetry = noop, signal } = options || {}; const maxAge = getRange(paramMaxAge); return { maxRetry, maxAge, minRetryDelay, maxRetryDelay, onBeforeRetry, f: paramMaxAge === 1 / 0, // Infinity signal }; }; const flush = (globalKey) => { if (!globalKey) return; delete _globalStore[globalKey]; }; const flushAll = () => { _globalStore = {}; }; const idmp = (globalKey, promiseFunc, options) => { if (!globalKey) { return promiseFunc(); } const { maxRetry, minRetryDelay, maxRetryDelay, maxAge, onBeforeRetry, f: isFiniteParamMaxAge, signal } = getOptions(options); _globalStore[globalKey] = _globalStore[globalKey] || [ 0, // [K.retryCount]: number 0, // [K.status]: Status [] // [K.pendingList]: Array<any> ]; const cache = _globalStore[globalKey]; const reset = () => { cache[ 1 /* status */ ] = 0; cache[ 3 /* resolvedData */ ] = cache[ 4 /* rejectionError */ ] = UNDEFINED; }; const doResolves = () => { const len = cache[ 2 /* pendingList */ ].length; for (let i = 0; i < len; ++i) { cache[ 2 /* pendingList */ ][i][0](cache[ 3 /* resolvedData */ ]); } cache[ 2 /* pendingList */ ] = []; if (!isFiniteParamMaxAge) { $timeout(() => { flush(globalKey); }, maxAge); } }; const doRejects = () => { const len = cache[ 2 /* pendingList */ ].length; let maxLen; maxLen = len - maxRetry; if (maxLen < 0 || !isFinite(len)) { maxLen = getMax(1, cache[ 2 /* pendingList */ ].length - 3); } for (let i = 0; i < maxLen; ++i) { cache[ 2 /* pendingList */ ][i][1](cache[ 4 /* rejectionError */ ]); } flush(globalKey); }; const executePromise = () => new Promise((resolve, reject) => { !cache[ 5 /* cachedPromiseFunc */ ] && (cache[ 5 /* cachedPromiseFunc */ ] = promiseFunc); if (cache[ 3 /* resolvedData */ ]) { resolve(cache[ 3 /* resolvedData */ ]); return; } if (signal) { if (signal.aborted) return; signal.addEventListener("abort", () => { reset(); cache[ 4 /* rejectionError */ ] = new DOMException( signal.reason, "AbortError" ); doRejects(); }); } if (cache[ 1 /* status */ ] === 0) { cache[ 1 /* status */ ] = 1; cache[ 2 /* pendingList */ ].push([resolve, reject]); cache[ 5 /* cachedPromiseFunc */ ]().then((data) => { { cache[ 3 /* resolvedData */ ] = data; } doResolves(); cache[ 1 /* status */ ] = 4; }).catch((err) => { cache[ 1 /* status */ ] = 3; cache[ 4 /* rejectionError */ ] = err; ++cache[ 0 /* retryCount */ ]; if (cache[ 0 /* retryCount */ ] > maxRetry) { doRejects(); } else { onBeforeRetry(err, { globalKey, retryCount: cache[ 0 /* retryCount */ ] }); reset(); const delay = getMin( maxRetryDelay, minRetryDelay * __pow(2, cache[ 0 /* retryCount */ ] - 1) ); $timeout(executePromise, delay); } }); } else if (cache[ 1 /* status */ ] === 1) { cache[ 2 /* pendingList */ ].push([resolve, reject]); } }); return executePromise(); }; idmp.flush = flush; idmp.flushAll = flushAll; export { idmp as default, getOptions, idmp };