@peerbit/time 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/aggregators.d.ts +3 -1
- package/dist/src/aggregators.d.ts.map +1 -1
- package/dist/src/aggregators.js +113 -48
- package/dist/src/aggregators.js.map +1 -1
- package/package.json +1 -1
- package/src/aggregators.ts +131 -54
|
@@ -4,6 +4,7 @@ export declare const debounceFixedInterval: <T extends (...args: any[]) => any |
|
|
|
4
4
|
}) => {
|
|
5
5
|
call: (...args: Parameters<T>) => Promise<void>;
|
|
6
6
|
close: () => void;
|
|
7
|
+
flush: () => Promise<void>;
|
|
7
8
|
};
|
|
8
9
|
export declare const debounceAccumulator: <K, T, V>(fn: (args: V) => any, create: () => {
|
|
9
10
|
delete: (key: K) => void;
|
|
@@ -17,8 +18,9 @@ export declare const debounceAccumulator: <K, T, V>(fn: (args: V) => any, create
|
|
|
17
18
|
add: (value: T) => Promise<void>;
|
|
18
19
|
delete: (key: K) => void;
|
|
19
20
|
size: () => number;
|
|
21
|
+
has: (key: K) => boolean;
|
|
20
22
|
invoke: () => Promise<void>;
|
|
21
23
|
close: () => void;
|
|
22
|
-
|
|
24
|
+
flush: () => Promise<void>;
|
|
23
25
|
};
|
|
24
26
|
//# sourceMappingURL=aggregators.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregators.d.ts","sourceRoot":"","sources":["../../src/aggregators.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,GACjC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,EAEhD,IAAI,CAAC,EACL,OAAO,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EAC9B,UAAU;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,KAC/D;
|
|
1
|
+
{"version":3,"file":"aggregators.d.ts","sourceRoot":"","sources":["../../src/aggregators.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,GACjC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,EAEhD,IAAI,CAAC,EACL,OAAO,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EAC9B,UAAU;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,KAC/D;IACF,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAuJ3B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC1C,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,EACpB,QAAQ,MAAM;IACb,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IACzB,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,EAAE,CAAC,CAAC;IACT,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;CACzB,EACD,OAAO,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EAC9B,UAAU;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;iBAajB,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC;kBAKhB,CAAC;;eAIJ,CAAC;kBAGM,OAAO,CAAC,IAAI,CAAC;iBAMpB,IAAI;iBAMJ,OAAO,CAAC,IAAI,CAAC;CAEzB,CAAC"}
|
package/dist/src/aggregators.js
CHANGED
|
@@ -1,110 +1,175 @@
|
|
|
1
1
|
export const debounceFixedInterval = (fn, delay, options) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
((error) => {
|
|
7
|
-
throw error;
|
|
2
|
+
const delayFn = typeof delay === "number" ? () => delay : delay;
|
|
3
|
+
const onError = options?.onError ??
|
|
4
|
+
((e) => {
|
|
5
|
+
throw e;
|
|
8
6
|
});
|
|
9
|
-
|
|
10
|
-
const leading = options?.leading !== undefined ? options.leading : true;
|
|
7
|
+
const leading = options?.leading ?? true;
|
|
11
8
|
let timeout = null;
|
|
12
9
|
let lastArgs = null;
|
|
13
10
|
let lastThis;
|
|
14
|
-
let pendingCall = false;
|
|
15
|
-
let isRunning = false;
|
|
16
|
-
|
|
17
|
-
let waitingResolvers = [];
|
|
18
|
-
// Track when the last invocation finished.
|
|
11
|
+
let pendingCall = false; // there is queued work for the *next* run
|
|
12
|
+
let isRunning = false; // fn is executing right now
|
|
13
|
+
let waitingResolvers = []; // resolve when *a* run completes
|
|
19
14
|
let lastInvokeTime = null;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
let forceNextImmediate = false;
|
|
16
|
+
// Completed run counter + precise run waiters
|
|
17
|
+
let completedRuns = 0;
|
|
18
|
+
let runWaiters = [];
|
|
19
|
+
const resolveRunWaiters = () => {
|
|
20
|
+
if (runWaiters.length === 0)
|
|
23
21
|
return;
|
|
22
|
+
const remaining = [];
|
|
23
|
+
for (const w of runWaiters) {
|
|
24
|
+
if (completedRuns >= w.target) {
|
|
25
|
+
w.resolve();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
remaining.push(w);
|
|
29
|
+
}
|
|
24
30
|
}
|
|
31
|
+
runWaiters = remaining;
|
|
32
|
+
};
|
|
33
|
+
const waitForRun = (target) => new Promise((resolve) => {
|
|
34
|
+
if (completedRuns >= target)
|
|
35
|
+
return resolve();
|
|
36
|
+
runWaiters.push({ target, resolve });
|
|
37
|
+
});
|
|
38
|
+
const invoke = async () => {
|
|
39
|
+
timeout = null;
|
|
40
|
+
if (!lastArgs)
|
|
41
|
+
return; // nothing to invoke
|
|
25
42
|
const args = lastArgs;
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
const ctx = lastThis;
|
|
44
|
+
lastArgs = null; // consume current args
|
|
45
|
+
pendingCall = false; // this run is for those args
|
|
28
46
|
isRunning = true;
|
|
29
47
|
try {
|
|
30
|
-
await Promise.resolve(fn.apply(
|
|
48
|
+
await Promise.resolve(fn.apply(ctx, args));
|
|
31
49
|
}
|
|
32
|
-
catch (
|
|
33
|
-
onError(
|
|
50
|
+
catch (err) {
|
|
51
|
+
onError(err);
|
|
34
52
|
}
|
|
35
53
|
finally {
|
|
36
54
|
isRunning = false;
|
|
37
55
|
lastInvokeTime = Date.now();
|
|
38
|
-
// Resolve all
|
|
39
|
-
|
|
56
|
+
// Resolve all call() promises queued for this completed run
|
|
57
|
+
const resolvers = waitingResolvers;
|
|
40
58
|
waitingResolvers = [];
|
|
41
|
-
|
|
59
|
+
for (const r of resolvers)
|
|
60
|
+
r();
|
|
61
|
+
// Mark completion and resolve any run-target waiters that are due
|
|
62
|
+
completedRuns++;
|
|
63
|
+
resolveRunWaiters();
|
|
64
|
+
// If new calls arrived during this run, schedule the next one
|
|
42
65
|
if (pendingCall) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
if (forceNextImmediate) {
|
|
67
|
+
forceNextImmediate = false;
|
|
68
|
+
timeout = setTimeout(invoke, 0);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const elapsed = Date.now() - (lastInvokeTime ?? 0);
|
|
72
|
+
const remaining = Math.max(delayFn() - elapsed, 0);
|
|
73
|
+
timeout = setTimeout(invoke, remaining);
|
|
74
|
+
}
|
|
46
75
|
}
|
|
47
76
|
}
|
|
48
77
|
};
|
|
49
|
-
|
|
78
|
+
// Use a normal function to preserve `this` from the call site
|
|
79
|
+
function debounced(...args) {
|
|
50
80
|
lastArgs = args;
|
|
51
81
|
lastThis = this;
|
|
52
82
|
pendingCall = true;
|
|
53
|
-
//
|
|
83
|
+
// Resolve after the next completed run
|
|
54
84
|
const p = new Promise((resolve) => {
|
|
55
85
|
waitingResolvers.push(resolve);
|
|
56
86
|
});
|
|
57
87
|
const now = Date.now();
|
|
58
88
|
if (!isRunning && !timeout) {
|
|
59
89
|
if (leading) {
|
|
60
|
-
// If leading is enabled, trigger immediately if it's the very first call
|
|
61
|
-
// or if enough time has passed since the last invocation.
|
|
62
90
|
if (lastInvokeTime === null || now - lastInvokeTime >= delayFn()) {
|
|
63
91
|
invoke();
|
|
64
92
|
}
|
|
65
93
|
else {
|
|
66
|
-
const
|
|
67
|
-
timeout = setTimeout(invoke,
|
|
94
|
+
const remaining = delayFn() - (now - lastInvokeTime);
|
|
95
|
+
timeout = setTimeout(invoke, remaining);
|
|
68
96
|
}
|
|
69
97
|
}
|
|
70
98
|
else {
|
|
71
|
-
// If not leading, always schedule a trailing invocation.
|
|
72
99
|
timeout = setTimeout(invoke, delayFn());
|
|
73
100
|
}
|
|
74
101
|
}
|
|
75
102
|
return p;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
103
|
+
}
|
|
104
|
+
const flush = () => {
|
|
105
|
+
if (isRunning) {
|
|
106
|
+
// If there are pending args, ensure a trailing run and make it immediate
|
|
107
|
+
const hadPendingArgs = !!lastArgs;
|
|
108
|
+
if (hadPendingArgs) {
|
|
109
|
+
pendingCall = true;
|
|
110
|
+
forceNextImmediate = true;
|
|
83
111
|
}
|
|
84
|
-
|
|
112
|
+
// Wait for the current run (+1) and, if needed, the immediate trailing run (+1)
|
|
113
|
+
const target = completedRuns + 1 + (hadPendingArgs ? 1 : 0);
|
|
114
|
+
return waitForRun(target);
|
|
115
|
+
}
|
|
116
|
+
// Not running
|
|
117
|
+
if (timeout) {
|
|
118
|
+
clearTimeout(timeout);
|
|
119
|
+
timeout = null;
|
|
120
|
+
}
|
|
121
|
+
if (lastArgs) {
|
|
122
|
+
const target = completedRuns + 1; // we'll trigger a run now
|
|
123
|
+
invoke();
|
|
124
|
+
return waitForRun(target);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// nothing to flush
|
|
128
|
+
return Promise.resolve();
|
|
129
|
+
}
|
|
85
130
|
};
|
|
131
|
+
const close = () => {
|
|
132
|
+
if (timeout !== null) {
|
|
133
|
+
clearTimeout(timeout);
|
|
134
|
+
timeout = null;
|
|
135
|
+
}
|
|
136
|
+
isRunning = false;
|
|
137
|
+
forceNextImmediate = false;
|
|
138
|
+
// no auto-resolving of pending promises on close()
|
|
139
|
+
};
|
|
140
|
+
return { call: debounced, close, flush };
|
|
86
141
|
};
|
|
87
142
|
export const debounceAccumulator = (fn, create, delay, options) => {
|
|
88
143
|
let accumulator = create();
|
|
89
|
-
const
|
|
144
|
+
const innerInvoke = async () => {
|
|
90
145
|
const toSend = accumulator.value;
|
|
91
146
|
accumulator = create();
|
|
92
147
|
await fn(toSend);
|
|
93
148
|
};
|
|
94
|
-
const
|
|
149
|
+
const deb = debounceFixedInterval(innerInvoke, delay, options);
|
|
95
150
|
return {
|
|
96
151
|
add: (value) => {
|
|
97
152
|
accumulator.add(value);
|
|
98
|
-
//
|
|
99
|
-
return
|
|
153
|
+
// resolves when the batch (which includes this value) runs
|
|
154
|
+
return deb.call();
|
|
100
155
|
},
|
|
101
156
|
delete: (key) => {
|
|
102
157
|
accumulator.delete(key);
|
|
103
158
|
},
|
|
104
159
|
size: () => accumulator.size(),
|
|
105
|
-
invoke,
|
|
106
|
-
close: () => close(),
|
|
107
160
|
has: (key) => accumulator.has(key),
|
|
161
|
+
// Run immediately, and **cancel** any pending scheduled run to avoid a trailing empty run.
|
|
162
|
+
invoke: async () => {
|
|
163
|
+
deb.close(); // cancel any pending timeout
|
|
164
|
+
await innerInvoke();
|
|
165
|
+
},
|
|
166
|
+
// Cancel pending schedule AND reset accumulator so size() === 0 afterward.
|
|
167
|
+
close: () => {
|
|
168
|
+
deb.close();
|
|
169
|
+
accumulator = create();
|
|
170
|
+
},
|
|
171
|
+
// If you exposed flush() before, keep passing it through:
|
|
172
|
+
flush: () => deb.flush?.() ?? Promise.resolve(),
|
|
108
173
|
};
|
|
109
174
|
};
|
|
110
175
|
//# sourceMappingURL=aggregators.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregators.js","sourceRoot":"","sources":["../../src/aggregators.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAGpC,EAAK,EACL,KAA8B,EAC9B,OAAiE,
|
|
1
|
+
{"version":3,"file":"aggregators.js","sourceRoot":"","sources":["../../src/aggregators.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/aggregators.ts
CHANGED
|
@@ -4,60 +4,100 @@ export const debounceFixedInterval = <
|
|
|
4
4
|
fn: T,
|
|
5
5
|
delay: number | (() => number),
|
|
6
6
|
options?: { onError?: (error: Error) => void; leading?: boolean },
|
|
7
|
-
): {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
): {
|
|
8
|
+
call: (...args: Parameters<T>) => Promise<void>;
|
|
9
|
+
close: () => void;
|
|
10
|
+
flush: () => Promise<void>;
|
|
11
|
+
} => {
|
|
12
|
+
const delayFn: () => number = typeof delay === "number" ? () => delay : delay;
|
|
11
13
|
const onError =
|
|
12
|
-
options?.onError
|
|
13
|
-
((
|
|
14
|
-
throw
|
|
14
|
+
options?.onError ??
|
|
15
|
+
((e: Error) => {
|
|
16
|
+
throw e;
|
|
15
17
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let timeout:
|
|
18
|
+
const leading = options?.leading ?? true;
|
|
19
|
+
|
|
20
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
19
21
|
let lastArgs: any[] | null = null;
|
|
20
22
|
let lastThis: any;
|
|
21
|
-
let pendingCall = false;
|
|
22
|
-
let isRunning = false;
|
|
23
|
-
|
|
24
|
-
let waitingResolvers: Array<() => void> = [];
|
|
25
|
-
// Track when the last invocation finished.
|
|
23
|
+
let pendingCall = false; // there is queued work for the *next* run
|
|
24
|
+
let isRunning = false; // fn is executing right now
|
|
25
|
+
let waitingResolvers: Array<() => void> = []; // resolve when *a* run completes
|
|
26
26
|
let lastInvokeTime: number | null = null;
|
|
27
|
+
let forceNextImmediate = false;
|
|
28
|
+
|
|
29
|
+
// Completed run counter + precise run waiters
|
|
30
|
+
let completedRuns = 0;
|
|
31
|
+
type RunWaiter = { target: number; resolve: () => void };
|
|
32
|
+
let runWaiters: RunWaiter[] = [];
|
|
33
|
+
|
|
34
|
+
const resolveRunWaiters = () => {
|
|
35
|
+
if (runWaiters.length === 0) return;
|
|
36
|
+
const remaining: RunWaiter[] = [];
|
|
37
|
+
for (const w of runWaiters) {
|
|
38
|
+
if (completedRuns >= w.target) {
|
|
39
|
+
w.resolve();
|
|
40
|
+
} else {
|
|
41
|
+
remaining.push(w);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
runWaiters = remaining;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const waitForRun = (target: number) =>
|
|
48
|
+
new Promise<void>((resolve) => {
|
|
49
|
+
if (completedRuns >= target) return resolve();
|
|
50
|
+
runWaiters.push({ target, resolve });
|
|
51
|
+
});
|
|
27
52
|
|
|
28
53
|
const invoke = async () => {
|
|
29
54
|
timeout = null;
|
|
30
|
-
if (!lastArgs)
|
|
31
|
-
|
|
32
|
-
}
|
|
55
|
+
if (!lastArgs) return; // nothing to invoke
|
|
56
|
+
|
|
33
57
|
const args = lastArgs;
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
const ctx = lastThis;
|
|
59
|
+
lastArgs = null; // consume current args
|
|
60
|
+
pendingCall = false; // this run is for those args
|
|
36
61
|
isRunning = true;
|
|
62
|
+
|
|
37
63
|
try {
|
|
38
|
-
await Promise.resolve(fn.apply(
|
|
39
|
-
} catch (
|
|
40
|
-
onError(
|
|
64
|
+
await Promise.resolve(fn.apply(ctx, args));
|
|
65
|
+
} catch (err) {
|
|
66
|
+
onError(err as Error);
|
|
41
67
|
} finally {
|
|
42
68
|
isRunning = false;
|
|
43
69
|
lastInvokeTime = Date.now();
|
|
44
|
-
|
|
45
|
-
|
|
70
|
+
|
|
71
|
+
// Resolve all call() promises queued for this completed run
|
|
72
|
+
const resolvers = waitingResolvers;
|
|
46
73
|
waitingResolvers = [];
|
|
47
|
-
|
|
74
|
+
for (const r of resolvers) r();
|
|
75
|
+
|
|
76
|
+
// Mark completion and resolve any run-target waiters that are due
|
|
77
|
+
completedRuns++;
|
|
78
|
+
resolveRunWaiters();
|
|
79
|
+
|
|
80
|
+
// If new calls arrived during this run, schedule the next one
|
|
48
81
|
if (pendingCall) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
82
|
+
if (forceNextImmediate) {
|
|
83
|
+
forceNextImmediate = false;
|
|
84
|
+
timeout = setTimeout(invoke, 0);
|
|
85
|
+
} else {
|
|
86
|
+
const elapsed = Date.now() - (lastInvokeTime ?? 0);
|
|
87
|
+
const remaining = Math.max(delayFn() - elapsed, 0);
|
|
88
|
+
timeout = setTimeout(invoke, remaining);
|
|
89
|
+
}
|
|
52
90
|
}
|
|
53
91
|
}
|
|
54
92
|
};
|
|
55
93
|
|
|
56
|
-
|
|
94
|
+
// Use a normal function to preserve `this` from the call site
|
|
95
|
+
function debounced(this: any, ...args: Parameters<T>): Promise<void> {
|
|
57
96
|
lastArgs = args;
|
|
58
97
|
lastThis = this;
|
|
59
98
|
pendingCall = true;
|
|
60
|
-
|
|
99
|
+
|
|
100
|
+
// Resolve after the next completed run
|
|
61
101
|
const p = new Promise<void>((resolve) => {
|
|
62
102
|
waitingResolvers.push(resolve);
|
|
63
103
|
});
|
|
@@ -65,31 +105,59 @@ export const debounceFixedInterval = <
|
|
|
65
105
|
const now = Date.now();
|
|
66
106
|
if (!isRunning && !timeout) {
|
|
67
107
|
if (leading) {
|
|
68
|
-
// If leading is enabled, trigger immediately if it's the very first call
|
|
69
|
-
// or if enough time has passed since the last invocation.
|
|
70
108
|
if (lastInvokeTime === null || now - lastInvokeTime >= delayFn()) {
|
|
71
109
|
invoke();
|
|
72
110
|
} else {
|
|
73
|
-
const
|
|
74
|
-
timeout = setTimeout(invoke,
|
|
111
|
+
const remaining = delayFn() - (now - lastInvokeTime);
|
|
112
|
+
timeout = setTimeout(invoke, remaining);
|
|
75
113
|
}
|
|
76
114
|
} else {
|
|
77
|
-
// If not leading, always schedule a trailing invocation.
|
|
78
115
|
timeout = setTimeout(invoke, delayFn());
|
|
79
116
|
}
|
|
80
117
|
}
|
|
81
118
|
return p;
|
|
82
|
-
}
|
|
119
|
+
}
|
|
83
120
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
121
|
+
const flush = (): Promise<void> => {
|
|
122
|
+
if (isRunning) {
|
|
123
|
+
// If there are pending args, ensure a trailing run and make it immediate
|
|
124
|
+
const hadPendingArgs = !!lastArgs;
|
|
125
|
+
if (hadPendingArgs) {
|
|
126
|
+
pendingCall = true;
|
|
127
|
+
forceNextImmediate = true;
|
|
90
128
|
}
|
|
91
|
-
|
|
129
|
+
// Wait for the current run (+1) and, if needed, the immediate trailing run (+1)
|
|
130
|
+
const target = completedRuns + 1 + (hadPendingArgs ? 1 : 0);
|
|
131
|
+
return waitForRun(target);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Not running
|
|
135
|
+
if (timeout) {
|
|
136
|
+
clearTimeout(timeout);
|
|
137
|
+
timeout = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (lastArgs) {
|
|
141
|
+
const target = completedRuns + 1; // we'll trigger a run now
|
|
142
|
+
invoke();
|
|
143
|
+
return waitForRun(target);
|
|
144
|
+
} else {
|
|
145
|
+
// nothing to flush
|
|
146
|
+
return Promise.resolve();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const close = () => {
|
|
151
|
+
if (timeout !== null) {
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
timeout = null;
|
|
154
|
+
}
|
|
155
|
+
isRunning = false;
|
|
156
|
+
forceNextImmediate = false;
|
|
157
|
+
// no auto-resolving of pending promises on close()
|
|
92
158
|
};
|
|
159
|
+
|
|
160
|
+
return { call: debounced, close, flush };
|
|
93
161
|
};
|
|
94
162
|
|
|
95
163
|
export const debounceAccumulator = <K, T, V>(
|
|
@@ -106,30 +174,39 @@ export const debounceAccumulator = <K, T, V>(
|
|
|
106
174
|
) => {
|
|
107
175
|
let accumulator = create();
|
|
108
176
|
|
|
109
|
-
const
|
|
177
|
+
const innerInvoke = async () => {
|
|
110
178
|
const toSend = accumulator.value;
|
|
111
179
|
accumulator = create();
|
|
112
180
|
await fn(toSend);
|
|
113
181
|
};
|
|
114
182
|
|
|
115
|
-
const
|
|
116
|
-
invoke,
|
|
117
|
-
delay,
|
|
118
|
-
options,
|
|
119
|
-
);
|
|
183
|
+
const deb = debounceFixedInterval(innerInvoke, delay, options);
|
|
120
184
|
|
|
121
185
|
return {
|
|
122
186
|
add: (value: T): Promise<void> => {
|
|
123
187
|
accumulator.add(value);
|
|
124
|
-
//
|
|
125
|
-
return
|
|
188
|
+
// resolves when the batch (which includes this value) runs
|
|
189
|
+
return deb.call();
|
|
126
190
|
},
|
|
127
191
|
delete: (key: K) => {
|
|
128
192
|
accumulator.delete(key);
|
|
129
193
|
},
|
|
130
194
|
size: () => accumulator.size(),
|
|
131
|
-
invoke,
|
|
132
|
-
close: () => close(),
|
|
133
195
|
has: (key: K) => accumulator.has(key),
|
|
196
|
+
|
|
197
|
+
// Run immediately, and **cancel** any pending scheduled run to avoid a trailing empty run.
|
|
198
|
+
invoke: async (): Promise<void> => {
|
|
199
|
+
deb.close(); // cancel any pending timeout
|
|
200
|
+
await innerInvoke();
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// Cancel pending schedule AND reset accumulator so size() === 0 afterward.
|
|
204
|
+
close: (): void => {
|
|
205
|
+
deb.close();
|
|
206
|
+
accumulator = create();
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
// If you exposed flush() before, keep passing it through:
|
|
210
|
+
flush: (): Promise<void> => deb.flush?.() ?? Promise.resolve(),
|
|
134
211
|
};
|
|
135
212
|
};
|