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
JavaScript
/*! 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
};