1 | ;
|
2 | const pDefer = require("p-defer");
|
3 | function mapAgeCleaner(map, property = 'maxAge') {
|
4 | let processingKey;
|
5 | let processingTimer;
|
6 | let processingDeferred;
|
7 | const cleanup = async () => {
|
8 | if (processingKey !== undefined) {
|
9 | // If we are already processing an item, we can safely exit
|
10 | return;
|
11 | }
|
12 | const setupTimer = async (item) => {
|
13 | processingDeferred = pDefer();
|
14 | const delay = item[1][property] - Date.now();
|
15 | if (delay <= 0) {
|
16 | // Remove the item immediately if the delay is equal to or below 0
|
17 | map.delete(item[0]);
|
18 | processingDeferred.resolve();
|
19 | return;
|
20 | }
|
21 | // Keep track of the current processed key
|
22 | processingKey = item[0];
|
23 | processingTimer = setTimeout(() => {
|
24 | // Remove the item when the timeout fires
|
25 | map.delete(item[0]);
|
26 | if (processingDeferred) {
|
27 | processingDeferred.resolve();
|
28 | }
|
29 | }, delay);
|
30 | // tslint:disable-next-line:strict-type-predicates
|
31 | if (typeof processingTimer.unref === 'function') {
|
32 | // Don't hold up the process from exiting
|
33 | processingTimer.unref();
|
34 | }
|
35 | return processingDeferred.promise;
|
36 | };
|
37 | try {
|
38 | for (const entry of map) {
|
39 | await setupTimer(entry);
|
40 | }
|
41 | }
|
42 | catch (_a) {
|
43 | // Do nothing if an error occurs, this means the timer was cleaned up and we should stop processing
|
44 | }
|
45 | processingKey = undefined;
|
46 | };
|
47 | const reset = () => {
|
48 | processingKey = undefined;
|
49 | if (processingTimer !== undefined) {
|
50 | clearTimeout(processingTimer);
|
51 | processingTimer = undefined;
|
52 | }
|
53 | if (processingDeferred !== undefined) { // tslint:disable-line:early-exit
|
54 | processingDeferred.reject(undefined);
|
55 | processingDeferred = undefined;
|
56 | }
|
57 | };
|
58 | const originalSet = map.set.bind(map);
|
59 | map.set = (key, value) => {
|
60 | if (map.has(key)) {
|
61 | // If the key already exist, remove it so we can add it back at the end of the map.
|
62 | map.delete(key);
|
63 | }
|
64 | // Call the original `map.set`
|
65 | const result = originalSet(key, value);
|
66 | // If we are already processing a key and the key added is the current processed key, stop processing it
|
67 | if (processingKey && processingKey === key) {
|
68 | reset();
|
69 | }
|
70 | // Always run the cleanup method in case it wasn't started yet
|
71 | cleanup(); // tslint:disable-line:no-floating-promises
|
72 | return result;
|
73 | };
|
74 | cleanup(); // tslint:disable-line:no-floating-promises
|
75 | return map;
|
76 | }
|
77 | module.exports = mapAgeCleaner;
|