@tanstack/pacer 0.5.0 → 0.7.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/cjs/async-debouncer.cjs +32 -17
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +51 -9
- package/dist/cjs/async-queuer.cjs +189 -191
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +151 -94
- package/dist/cjs/async-rate-limiter.cjs +23 -13
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +54 -5
- package/dist/cjs/async-throttler.cjs +38 -17
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +51 -8
- package/dist/cjs/batcher.cjs +138 -0
- package/dist/cjs/batcher.cjs.map +1 -0
- package/dist/cjs/batcher.d.cts +149 -0
- package/dist/cjs/debouncer.cjs +3 -4
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +1 -2
- package/dist/cjs/index.cjs +3 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/queuer.cjs +68 -41
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +123 -92
- package/dist/cjs/rate-limiter.cjs +3 -4
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +1 -2
- package/dist/cjs/throttler.cjs +3 -4
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +1 -2
- package/dist/cjs/types.d.cts +1 -0
- package/dist/esm/async-debouncer.d.ts +51 -9
- package/dist/esm/async-debouncer.js +32 -17
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +151 -94
- package/dist/esm/async-queuer.js +189 -191
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +54 -5
- package/dist/esm/async-rate-limiter.js +23 -13
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +51 -8
- package/dist/esm/async-throttler.js +38 -17
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/batcher.d.ts +149 -0
- package/dist/esm/batcher.js +138 -0
- package/dist/esm/batcher.js.map +1 -0
- package/dist/esm/debouncer.d.ts +1 -2
- package/dist/esm/debouncer.js +3 -4
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/queuer.d.ts +123 -92
- package/dist/esm/queuer.js +68 -41
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +1 -2
- package/dist/esm/rate-limiter.js +3 -4
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +1 -2
- package/dist/esm/throttler.js +3 -4
- package/dist/esm/throttler.js.map +1 -1
- package/dist/esm/types.d.ts +1 -0
- package/package.json +11 -1
- package/src/async-debouncer.ts +76 -20
- package/src/async-queuer.ts +309 -275
- package/src/async-rate-limiter.ts +73 -16
- package/src/async-throttler.ts +84 -20
- package/src/batcher.ts +253 -0
- package/src/debouncer.ts +3 -4
- package/src/index.ts +1 -0
- package/src/queuer.ts +142 -98
- package/src/rate-limiter.ts +3 -4
- package/src/throttler.ts +3 -4
- package/src/types.ts +3 -0
|
@@ -15,7 +15,9 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
|
|
|
15
15
|
*/
|
|
16
16
|
limit: number | ((rateLimiter: AsyncRateLimiter<TFn>) => number);
|
|
17
17
|
/**
|
|
18
|
-
* Optional error handler for when the rate-limited function throws
|
|
18
|
+
* Optional error handler for when the rate-limited function throws.
|
|
19
|
+
* If provided, the handler will be called with the error and rate limiter instance.
|
|
20
|
+
* This can be used alongside throwOnError - the handler will be called before any error is thrown.
|
|
19
21
|
*/
|
|
20
22
|
onError?: (error: unknown, rateLimiter: AsyncRateLimiter<TFn>) => void;
|
|
21
23
|
/**
|
|
@@ -30,6 +32,12 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
|
|
|
30
32
|
* Optional function to call when the rate-limited function is executed
|
|
31
33
|
*/
|
|
32
34
|
onSuccess?: (result: ReturnType<TFn>, rateLimiter: AsyncRateLimiter<TFn>) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to throw errors when they occur.
|
|
37
|
+
* Defaults to true if no onError handler is provided, false if an onError handler is provided.
|
|
38
|
+
* Can be explicitly set to override these defaults.
|
|
39
|
+
*/
|
|
40
|
+
throwOnError?: boolean;
|
|
33
41
|
/**
|
|
34
42
|
* Time window in milliseconds within which the limit applies.
|
|
35
43
|
* Can be a number or a function that returns a number.
|
|
@@ -67,11 +75,29 @@ export interface AsyncRateLimiterOptions<TFn extends AnyAsyncFunction> {
|
|
|
67
75
|
* Rate limiting is best used for hard API limits or resource constraints. For UI updates or
|
|
68
76
|
* smoothing out frequent events, throttling or debouncing usually provide better user experience.
|
|
69
77
|
*
|
|
78
|
+
* Error Handling:
|
|
79
|
+
* - If an `onError` handler is provided, it will be called with the error and rate limiter instance
|
|
80
|
+
* - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
|
|
81
|
+
* - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
|
|
82
|
+
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
83
|
+
* - The error state can be checked using the underlying AsyncRateLimiter instance
|
|
84
|
+
* - Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler
|
|
85
|
+
*
|
|
70
86
|
* @example
|
|
71
87
|
* ```ts
|
|
72
88
|
* const rateLimiter = new AsyncRateLimiter(
|
|
73
89
|
* async (id: string) => await api.getData(id),
|
|
74
|
-
* {
|
|
90
|
+
* {
|
|
91
|
+
* limit: 5,
|
|
92
|
+
* window: 1000,
|
|
93
|
+
* windowType: 'sliding',
|
|
94
|
+
* onError: (error) => {
|
|
95
|
+
* console.error('API call failed:', error);
|
|
96
|
+
* },
|
|
97
|
+
* onReject: (limiter) => {
|
|
98
|
+
* console.log(`Rate limit exceeded. Try again in ${limiter.getMsUntilNextWindow()}ms`);
|
|
99
|
+
* }
|
|
100
|
+
* }
|
|
75
101
|
* );
|
|
76
102
|
*
|
|
77
103
|
* // Will execute immediately until limit reached, then block
|
|
@@ -92,13 +118,12 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
92
118
|
constructor(fn: TFn, initialOptions: AsyncRateLimiterOptions<TFn>);
|
|
93
119
|
/**
|
|
94
120
|
* Updates the rate limiter options
|
|
95
|
-
* Returns the new options state
|
|
96
121
|
*/
|
|
97
122
|
setOptions(newOptions: Partial<AsyncRateLimiterOptions<TFn>>): void;
|
|
98
123
|
/**
|
|
99
124
|
* Returns the current rate limiter options
|
|
100
125
|
*/
|
|
101
|
-
getOptions():
|
|
126
|
+
getOptions(): AsyncRateLimiterOptions<TFn>;
|
|
102
127
|
/**
|
|
103
128
|
* Returns the current enabled state of the rate limiter
|
|
104
129
|
*/
|
|
@@ -116,6 +141,19 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
116
141
|
* Will reject execution if the number of calls in the current window exceeds the limit.
|
|
117
142
|
* If execution is allowed, waits for any previous execution to complete before proceeding.
|
|
118
143
|
*
|
|
144
|
+
* Error Handling:
|
|
145
|
+
* - If the rate-limited function throws and no `onError` handler is configured,
|
|
146
|
+
* the error will be thrown from this method.
|
|
147
|
+
* - If an `onError` handler is configured, errors will be caught and passed to the handler,
|
|
148
|
+
* and this method will return undefined.
|
|
149
|
+
* - If the rate limit is exceeded, the execution will be rejected and the `onReject` handler
|
|
150
|
+
* will be called if configured.
|
|
151
|
+
* - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
|
|
152
|
+
* - Rate limit rejections can be tracked using `getRejectionCount()`.
|
|
153
|
+
*
|
|
154
|
+
* @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
|
|
155
|
+
* @throws The error from the rate-limited function if no onError handler is configured
|
|
156
|
+
*
|
|
119
157
|
* @example
|
|
120
158
|
* ```ts
|
|
121
159
|
* const rateLimiter = new AsyncRateLimiter(fn, { limit: 5, window: 1000 });
|
|
@@ -128,7 +166,7 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
128
166
|
* ```
|
|
129
167
|
*/
|
|
130
168
|
maybeExecute(...args: Parameters<TFn>): Promise<ReturnType<TFn> | undefined>;
|
|
131
|
-
private
|
|
169
|
+
private execute;
|
|
132
170
|
private rejectFunction;
|
|
133
171
|
private cleanupOldExecutions;
|
|
134
172
|
/**
|
|
@@ -187,6 +225,14 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
187
225
|
* Consider using throttle() or debounce() if you need more intelligent execution control. Use rate limiting when you specifically
|
|
188
226
|
* need to enforce a hard limit on the number of executions within a time period.
|
|
189
227
|
*
|
|
228
|
+
* Error Handling:
|
|
229
|
+
* - If an `onError` handler is provided, it will be called with the error and rate limiter instance
|
|
230
|
+
* - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
|
|
231
|
+
* - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
|
|
232
|
+
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
233
|
+
* - The error state can be checked using the underlying AsyncRateLimiter instance
|
|
234
|
+
* - Rate limit rejections (when limit is exceeded) are handled separately from execution errors via the `onReject` handler
|
|
235
|
+
*
|
|
190
236
|
* @example
|
|
191
237
|
* ```ts
|
|
192
238
|
* // Rate limit to 5 calls per minute with a sliding window
|
|
@@ -194,6 +240,9 @@ export declare class AsyncRateLimiter<TFn extends AnyAsyncFunction> {
|
|
|
194
240
|
* limit: 5,
|
|
195
241
|
* window: 60000,
|
|
196
242
|
* windowType: 'sliding',
|
|
243
|
+
* onError: (error) => {
|
|
244
|
+
* console.error('API call failed:', error);
|
|
245
|
+
* },
|
|
197
246
|
* onReject: (rateLimiter) => {
|
|
198
247
|
* console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`);
|
|
199
248
|
* }
|
|
@@ -4,12 +4,6 @@ const utils = require("./utils.cjs");
|
|
|
4
4
|
const defaultOptions = {
|
|
5
5
|
enabled: true,
|
|
6
6
|
leading: true,
|
|
7
|
-
onError: () => {
|
|
8
|
-
},
|
|
9
|
-
onSettled: () => {
|
|
10
|
-
},
|
|
11
|
-
onSuccess: () => {
|
|
12
|
-
},
|
|
13
7
|
trailing: true,
|
|
14
8
|
wait: 0
|
|
15
9
|
};
|
|
@@ -24,14 +18,15 @@ class AsyncThrottler {
|
|
|
24
18
|
this._settleCount = 0;
|
|
25
19
|
this._successCount = 0;
|
|
26
20
|
this._timeoutId = null;
|
|
21
|
+
this._resolvePreviousPromise = null;
|
|
27
22
|
this._options = {
|
|
28
23
|
...defaultOptions,
|
|
29
|
-
...initialOptions
|
|
24
|
+
...initialOptions,
|
|
25
|
+
throwOnError: initialOptions.throwOnError ?? !initialOptions.onError
|
|
30
26
|
};
|
|
31
27
|
}
|
|
32
28
|
/**
|
|
33
29
|
* Updates the throttler options
|
|
34
|
-
* Returns the new options state
|
|
35
30
|
*/
|
|
36
31
|
setOptions(newOptions) {
|
|
37
32
|
this._options = { ...this._options, ...newOptions };
|
|
@@ -49,7 +44,7 @@ class AsyncThrottler {
|
|
|
49
44
|
* Returns the current enabled state of the throttler
|
|
50
45
|
*/
|
|
51
46
|
getEnabled() {
|
|
52
|
-
return utils.parseFunctionOrValue(this._options.enabled, this);
|
|
47
|
+
return !!utils.parseFunctionOrValue(this._options.enabled, this);
|
|
53
48
|
}
|
|
54
49
|
/**
|
|
55
50
|
* Returns the current wait time in milliseconds
|
|
@@ -58,19 +53,31 @@ class AsyncThrottler {
|
|
|
58
53
|
return utils.parseFunctionOrValue(this._options.wait, this);
|
|
59
54
|
}
|
|
60
55
|
/**
|
|
61
|
-
* Attempts to execute the throttled function
|
|
62
|
-
* If a call is already in progress, it may be blocked or queued depending on the `wait` option
|
|
56
|
+
* Attempts to execute the throttled function.
|
|
57
|
+
* If a call is already in progress, it may be blocked or queued depending on the `wait` option.
|
|
58
|
+
*
|
|
59
|
+
* Error Handling:
|
|
60
|
+
* - If the throttled function throws and no `onError` handler is configured,
|
|
61
|
+
* the error will be thrown from this method.
|
|
62
|
+
* - If an `onError` handler is configured, errors will be caught and passed to the handler,
|
|
63
|
+
* and this method will return undefined.
|
|
64
|
+
* - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
|
|
65
|
+
*
|
|
66
|
+
* @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
|
|
67
|
+
* @throws The error from the throttled function if no onError handler is configured
|
|
63
68
|
*/
|
|
64
69
|
async maybeExecute(...args) {
|
|
65
70
|
const now = Date.now();
|
|
66
71
|
const timeSinceLastExecution = now - this._lastExecutionTime;
|
|
67
72
|
const wait = this.getWait();
|
|
73
|
+
this.resolvePreviousPromise();
|
|
68
74
|
if (this._options.leading && timeSinceLastExecution >= wait) {
|
|
69
|
-
await this.
|
|
75
|
+
await this.execute(...args);
|
|
70
76
|
return this._lastResult;
|
|
71
77
|
} else {
|
|
72
78
|
this._lastArgs = args;
|
|
73
79
|
return new Promise((resolve) => {
|
|
80
|
+
this._resolvePreviousPromise = resolve;
|
|
74
81
|
if (this._timeoutId) {
|
|
75
82
|
clearTimeout(this._timeoutId);
|
|
76
83
|
}
|
|
@@ -79,35 +86,48 @@ class AsyncThrottler {
|
|
|
79
86
|
const timeoutDuration = wait - _timeSinceLastExecution;
|
|
80
87
|
this._timeoutId = setTimeout(async () => {
|
|
81
88
|
if (this._lastArgs !== void 0) {
|
|
82
|
-
await this.
|
|
89
|
+
await this.execute(...this._lastArgs);
|
|
83
90
|
}
|
|
91
|
+
this._resolvePreviousPromise = null;
|
|
84
92
|
resolve(this._lastResult);
|
|
85
93
|
}, timeoutDuration);
|
|
86
94
|
}
|
|
87
95
|
});
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
|
-
async
|
|
98
|
+
async execute(...args) {
|
|
99
|
+
var _a, _b, _c, _d, _e, _f;
|
|
91
100
|
if (!this.getEnabled() || this._isExecuting) return void 0;
|
|
92
101
|
this._abortController = new AbortController();
|
|
93
102
|
try {
|
|
94
103
|
this._isExecuting = true;
|
|
95
104
|
this._lastResult = await this.fn(...args);
|
|
96
105
|
this._successCount++;
|
|
97
|
-
this._options.onSuccess(this._lastResult, this);
|
|
106
|
+
(_b = (_a = this._options).onSuccess) == null ? void 0 : _b.call(_a, this._lastResult, this);
|
|
98
107
|
} catch (error) {
|
|
99
108
|
this._errorCount++;
|
|
100
|
-
this._options.onError(error, this);
|
|
109
|
+
(_d = (_c = this._options).onError) == null ? void 0 : _d.call(_c, error, this);
|
|
110
|
+
if (this._options.throwOnError) {
|
|
111
|
+
throw error;
|
|
112
|
+
} else {
|
|
113
|
+
console.error(error);
|
|
114
|
+
}
|
|
101
115
|
} finally {
|
|
102
116
|
this._isExecuting = false;
|
|
103
117
|
this._settleCount++;
|
|
104
118
|
this._abortController = null;
|
|
105
119
|
this._lastExecutionTime = Date.now();
|
|
106
120
|
this._nextExecutionTime = this._lastExecutionTime + this.getWait();
|
|
107
|
-
this._options.onSettled(this);
|
|
121
|
+
(_f = (_e = this._options).onSettled) == null ? void 0 : _f.call(_e, this);
|
|
108
122
|
}
|
|
109
123
|
return this._lastResult;
|
|
110
124
|
}
|
|
125
|
+
resolvePreviousPromise() {
|
|
126
|
+
if (this._resolvePreviousPromise) {
|
|
127
|
+
this._resolvePreviousPromise(this._lastResult);
|
|
128
|
+
this._resolvePreviousPromise = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
111
131
|
/**
|
|
112
132
|
* Cancels any pending execution or aborts any execution in progress
|
|
113
133
|
*/
|
|
@@ -120,6 +140,7 @@ class AsyncThrottler {
|
|
|
120
140
|
this._abortController.abort();
|
|
121
141
|
this._abortController = null;
|
|
122
142
|
}
|
|
143
|
+
this.resolvePreviousPromise();
|
|
123
144
|
this._lastArgs = void 0;
|
|
124
145
|
}
|
|
125
146
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async-throttler.cjs","sources":["../../src/async-throttler.ts"],"sourcesContent":["import { parseFunctionOrValue } from './utils'\nimport type { AnyAsyncFunction } from './types'\n\n/**\n * Options for configuring an async throttled function\n */\nexport interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions.\n * Can be a boolean or a function that returns a boolean.\n * Defaults to true.\n */\n enabled?: boolean | ((throttler: AsyncThrottler<TFn>) => boolean)\n /**\n * Whether to execute the function immediately when called\n * Defaults to true\n */\n leading?: boolean\n /**\n * Optional error handler for when the throttled function throws\n */\n onError?: (error: unknown, asyncThrottler: AsyncThrottler<TFn>) => void\n /**\n * Optional function to call when the throttled function is executed\n */\n onSettled?: (asyncThrottler: AsyncThrottler<TFn>) => void\n /**\n * Optional function to call when the throttled function is executed\n */\n onSuccess?: (\n result: ReturnType<TFn>,\n asyncThrottler: AsyncThrottler<TFn>,\n ) => void\n /**\n * Whether to execute the function on the trailing edge of the wait period\n * Defaults to true\n */\n trailing?: boolean\n /**\n * Time window in milliseconds during which the function can only be executed once.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((throttler: AsyncThrottler<TFn>) => number)\n}\n\nconst defaultOptions: Required<AsyncThrottlerOptions<any>> = {\n enabled: true,\n leading: true,\n onError: () => {},\n onSettled: () => {},\n onSuccess: () => {},\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async throttled function.\n *\n * Throttling limits how often a function can be executed, allowing only one execution within a specified time window.\n * Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a\n * regular interval regardless of how often it's called.\n *\n * Unlike the non-async Throttler, this async version supports returning values from the throttled function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the throttled function.\n *\n * This is useful for rate-limiting API calls, handling scroll/resize events, or any scenario where you want to\n * ensure a maximum execution frequency.\n *\n * @example\n * ```ts\n * const throttler = new AsyncThrottler(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, { wait: 1000 });\n *\n * // Will only execute once per second no matter how often called\n * // Returns the API response directly\n * const result = await throttler.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncThrottler<TFn extends AnyAsyncFunction> {\n private _options: Required<AsyncThrottlerOptions<TFn>>\n private _abortController: AbortController | null = null\n private _errorCount = 0\n private _isExecuting = false\n private _lastArgs: Parameters<TFn> | undefined\n private _lastExecutionTime = 0\n private _lastResult: ReturnType<TFn> | undefined\n private _nextExecutionTime = 0\n private _settleCount = 0\n private _successCount = 0\n private _timeoutId: NodeJS.Timeout | null = null\n\n constructor(\n private fn: TFn,\n initialOptions: AsyncThrottlerOptions<TFn>,\n ) {\n this._options = {\n ...defaultOptions,\n ...initialOptions,\n }\n }\n\n /**\n * Updates the throttler options\n * Returns the new options state\n */\n setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn>>): void {\n this._options = { ...this._options, ...newOptions }\n\n // End the pending state if the debouncer is disabled\n if (!this._options.enabled) {\n this.cancel()\n }\n }\n\n /**\n * Returns the current options\n */\n getOptions(): Required<AsyncThrottlerOptions<TFn>> {\n return this._options\n }\n\n /**\n * Returns the current enabled state of the throttler\n */\n getEnabled(): boolean {\n return parseFunctionOrValue(this._options.enabled, this)\n }\n\n /**\n * Returns the current wait time in milliseconds\n */\n getWait(): number {\n return parseFunctionOrValue(this._options.wait, this)\n }\n\n /**\n * Attempts to execute the throttled function\n * If a call is already in progress, it may be blocked or queued depending on the `wait` option\n */\n async maybeExecute(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n const now = Date.now()\n const timeSinceLastExecution = now - this._lastExecutionTime\n const wait = this.getWait()\n\n // Handle leading execution\n if (this._options.leading && timeSinceLastExecution >= wait) {\n await this.executeFunction(...args)\n return this._lastResult\n } else {\n // Store the most recent arguments for potential trailing execution\n this._lastArgs = args\n\n return new Promise((resolve) => {\n // Clear any existing timeout to ensure we use the latest arguments\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n }\n\n // Set up trailing execution if enabled\n if (this._options.trailing) {\n const _timeSinceLastExecution = this._lastExecutionTime\n ? now - this._lastExecutionTime\n : 0\n const timeoutDuration = wait - _timeSinceLastExecution\n this._timeoutId = setTimeout(async () => {\n if (this._lastArgs !== undefined) {\n await this.executeFunction(...this._lastArgs)\n }\n resolve(this._lastResult)\n }, timeoutDuration)\n }\n })\n }\n }\n\n private async executeFunction(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n if (!this.getEnabled() || this._isExecuting) return undefined\n this._abortController = new AbortController()\n try {\n this._isExecuting = true\n this._lastResult = await this.fn(...args) // EXECUTE!\n this._successCount++\n this._options.onSuccess(this._lastResult!, this)\n } catch (error) {\n this._errorCount++\n this._options.onError(error, this)\n } finally {\n this._isExecuting = false\n this._settleCount++\n this._abortController = null\n this._lastExecutionTime = Date.now()\n this._nextExecutionTime = this._lastExecutionTime + this.getWait()\n this._options.onSettled(this)\n }\n return this._lastResult\n }\n\n /**\n * Cancels any pending execution or aborts any execution in progress\n */\n cancel(): void {\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n this._timeoutId = null\n }\n if (this._abortController) {\n this._abortController.abort()\n this._abortController = null\n }\n this._lastArgs = undefined\n }\n\n /**\n * Returns the last execution time\n */\n getLastExecutionTime(): number {\n return this._lastExecutionTime\n }\n\n /**\n * Returns the next execution time\n */\n getNextExecutionTime(): number {\n return this._nextExecutionTime\n }\n\n /**\n * Returns the last result of the debounced function\n */\n getLastResult(): ReturnType<TFn> | undefined {\n return this._lastResult\n }\n\n /**\n * Returns the number of times the function has been executed successfully\n */\n getSuccessCount(): number {\n return this._successCount\n }\n\n /**\n * Returns the number of times the function has settled (completed or errored)\n */\n getSettleCount(): number {\n return this._settleCount\n }\n\n /**\n * Returns the number of times the function has errored\n */\n getErrorCount(): number {\n return this._errorCount\n }\n\n /**\n * Returns the current pending state\n */\n getIsPending(): boolean {\n return this.getEnabled() && !!this._timeoutId\n }\n\n /**\n * Returns the current executing state\n */\n getIsExecuting(): boolean {\n return this._isExecuting\n }\n}\n\n/**\n * Creates an async throttled function that limits how often the function can execute.\n * The throttled function will execute at most once per wait period, even if called multiple times.\n * If called while executing, it will wait until execution completes before scheduling the next call.\n *\n * Unlike the non-async Throttler, this async version supports returning values from the throttled function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the throttled function.\n *\n * @example\n * ```ts\n * const throttled = asyncThrottle(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, { wait: 1000 });\n *\n * // This will execute at most once per second\n * // Returns the API response directly\n * const result = await throttled(inputElement.value);\n * ```\n */\nexport function asyncThrottle<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncThrottlerOptions<TFn>,\n) {\n const asyncThrottler = new AsyncThrottler(fn, initialOptions)\n return asyncThrottler.maybeExecute.bind(asyncThrottler)\n}\n"],"names":["parseFunctionOrValue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"async-throttler.cjs","sources":["../../src/async-throttler.ts"],"sourcesContent":["import { parseFunctionOrValue } from './utils'\nimport type { AnyAsyncFunction, OptionalKeys } from './types'\n\n/**\n * Options for configuring an async throttled function\n */\nexport interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {\n /**\n * Whether the throttler is enabled. When disabled, maybeExecute will not trigger any executions.\n * Can be a boolean or a function that returns a boolean.\n * Defaults to true.\n */\n enabled?: boolean | ((throttler: AsyncThrottler<TFn>) => boolean)\n /**\n * Whether to execute the function immediately when called\n * Defaults to true\n */\n leading?: boolean\n /**\n * Optional error handler for when the throttled function throws.\n * If provided, the handler will be called with the error and throttler instance.\n * This can be used alongside throwOnError - the handler will be called before any error is thrown.\n */\n onError?: (error: unknown, asyncThrottler: AsyncThrottler<TFn>) => void\n /**\n * Optional function to call when the throttled function is executed\n */\n onSettled?: (asyncThrottler: AsyncThrottler<TFn>) => void\n /**\n * Optional function to call when the throttled function is executed\n */\n onSuccess?: (\n result: ReturnType<TFn>,\n asyncThrottler: AsyncThrottler<TFn>,\n ) => void\n /**\n * Whether to throw errors when they occur.\n * Defaults to true if no onError handler is provided, false if an onError handler is provided.\n * Can be explicitly set to override these defaults.\n */\n throwOnError?: boolean\n /**\n * Whether to execute the function on the trailing edge of the wait period\n * Defaults to true\n */\n trailing?: boolean\n /**\n * Time window in milliseconds during which the function can only be executed once.\n * Can be a number or a function that returns a number.\n * Defaults to 0ms\n */\n wait: number | ((throttler: AsyncThrottler<TFn>) => number)\n}\n\ntype AsyncThrottlerOptionsWithOptionalCallbacks = OptionalKeys<\n AsyncThrottlerOptions<any>,\n 'onError' | 'onSettled' | 'onSuccess'\n>\n\nconst defaultOptions: AsyncThrottlerOptionsWithOptionalCallbacks = {\n enabled: true,\n leading: true,\n trailing: true,\n wait: 0,\n}\n\n/**\n * A class that creates an async throttled function.\n *\n * Throttling limits how often a function can be executed, allowing only one execution within a specified time window.\n * Unlike debouncing which resets the delay timer on each call, throttling ensures the function executes at a\n * regular interval regardless of how often it's called.\n *\n * Unlike the non-async Throttler, this async version supports returning values from the throttled function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the throttled function.\n *\n * This is useful for rate-limiting API calls, handling scroll/resize events, or any scenario where you want to\n * ensure a maximum execution frequency.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and throttler instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n * - The error state can be checked using the underlying AsyncThrottler instance\n *\n * @example\n * ```ts\n * const throttler = new AsyncThrottler(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, {\n * wait: 1000,\n * onError: (error) => {\n * console.error('API call failed:', error);\n * }\n * });\n *\n * // Will only execute once per second no matter how often called\n * // Returns the API response directly\n * const result = await throttler.maybeExecute(inputElement.value);\n * ```\n */\nexport class AsyncThrottler<TFn extends AnyAsyncFunction> {\n private _options: AsyncThrottlerOptionsWithOptionalCallbacks\n private _abortController: AbortController | null = null\n private _errorCount = 0\n private _isExecuting = false\n private _lastArgs: Parameters<TFn> | undefined\n private _lastExecutionTime = 0\n private _lastResult: ReturnType<TFn> | undefined\n private _nextExecutionTime = 0\n private _settleCount = 0\n private _successCount = 0\n private _timeoutId: NodeJS.Timeout | null = null\n private _resolvePreviousPromise:\n | ((value?: ReturnType<TFn> | undefined) => void)\n | null = null\n\n constructor(\n private fn: TFn,\n initialOptions: AsyncThrottlerOptions<TFn>,\n ) {\n this._options = {\n ...defaultOptions,\n ...initialOptions,\n throwOnError: initialOptions.throwOnError ?? !initialOptions.onError,\n }\n }\n\n /**\n * Updates the throttler options\n */\n setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn>>): void {\n this._options = { ...this._options, ...newOptions }\n\n // End the pending state if the debouncer is disabled\n if (!this._options.enabled) {\n this.cancel()\n }\n }\n\n /**\n * Returns the current options\n */\n getOptions(): AsyncThrottlerOptions<TFn> {\n return this._options\n }\n\n /**\n * Returns the current enabled state of the throttler\n */\n getEnabled(): boolean {\n return !!parseFunctionOrValue(this._options.enabled, this)\n }\n\n /**\n * Returns the current wait time in milliseconds\n */\n getWait(): number {\n return parseFunctionOrValue(this._options.wait, this)\n }\n\n /**\n * Attempts to execute the throttled function.\n * If a call is already in progress, it may be blocked or queued depending on the `wait` option.\n *\n * Error Handling:\n * - If the throttled function throws and no `onError` handler is configured,\n * the error will be thrown from this method.\n * - If an `onError` handler is configured, errors will be caught and passed to the handler,\n * and this method will return undefined.\n * - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.\n *\n * @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError\n * @throws The error from the throttled function if no onError handler is configured\n */\n async maybeExecute(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n const now = Date.now()\n const timeSinceLastExecution = now - this._lastExecutionTime\n const wait = this.getWait()\n\n this.resolvePreviousPromise()\n\n // Handle leading execution\n if (this._options.leading && timeSinceLastExecution >= wait) {\n await this.execute(...args)\n return this._lastResult\n } else {\n // Store the most recent arguments for potential trailing execution\n this._lastArgs = args\n\n return new Promise((resolve) => {\n this._resolvePreviousPromise = resolve\n // Clear any existing timeout to ensure we use the latest arguments\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n }\n\n // Set up trailing execution if enabled\n if (this._options.trailing) {\n const _timeSinceLastExecution = this._lastExecutionTime\n ? now - this._lastExecutionTime\n : 0\n const timeoutDuration = wait - _timeSinceLastExecution\n this._timeoutId = setTimeout(async () => {\n if (this._lastArgs !== undefined) {\n await this.execute(...this._lastArgs)\n }\n this._resolvePreviousPromise = null\n resolve(this._lastResult)\n }, timeoutDuration)\n }\n })\n }\n }\n\n private async execute(\n ...args: Parameters<TFn>\n ): Promise<ReturnType<TFn> | undefined> {\n if (!this.getEnabled() || this._isExecuting) return undefined\n this._abortController = new AbortController()\n try {\n this._isExecuting = true\n this._lastResult = await this.fn(...args) // EXECUTE!\n this._successCount++\n this._options.onSuccess?.(this._lastResult!, this)\n } catch (error) {\n this._errorCount++\n this._options.onError?.(error, this)\n if (this._options.throwOnError) {\n throw error\n } else {\n console.error(error)\n }\n } finally {\n this._isExecuting = false\n this._settleCount++\n this._abortController = null\n this._lastExecutionTime = Date.now()\n this._nextExecutionTime = this._lastExecutionTime + this.getWait()\n this._options.onSettled?.(this)\n }\n return this._lastResult\n }\n\n private resolvePreviousPromise(): void {\n if (this._resolvePreviousPromise) {\n this._resolvePreviousPromise(this._lastResult)\n this._resolvePreviousPromise = null\n }\n }\n\n /**\n * Cancels any pending execution or aborts any execution in progress\n */\n cancel(): void {\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n this._timeoutId = null\n }\n if (this._abortController) {\n this._abortController.abort()\n this._abortController = null\n }\n this.resolvePreviousPromise()\n this._lastArgs = undefined\n }\n\n /**\n * Returns the last execution time\n */\n getLastExecutionTime(): number {\n return this._lastExecutionTime\n }\n\n /**\n * Returns the next execution time\n */\n getNextExecutionTime(): number {\n return this._nextExecutionTime\n }\n\n /**\n * Returns the last result of the debounced function\n */\n getLastResult(): ReturnType<TFn> | undefined {\n return this._lastResult\n }\n\n /**\n * Returns the number of times the function has been executed successfully\n */\n getSuccessCount(): number {\n return this._successCount\n }\n\n /**\n * Returns the number of times the function has settled (completed or errored)\n */\n getSettleCount(): number {\n return this._settleCount\n }\n\n /**\n * Returns the number of times the function has errored\n */\n getErrorCount(): number {\n return this._errorCount\n }\n\n /**\n * Returns the current pending state\n */\n getIsPending(): boolean {\n return this.getEnabled() && !!this._timeoutId\n }\n\n /**\n * Returns the current executing state\n */\n getIsExecuting(): boolean {\n return this._isExecuting\n }\n}\n\n/**\n * Creates an async throttled function that limits how often the function can execute.\n * The throttled function will execute at most once per wait period, even if called multiple times.\n * If called while executing, it will wait until execution completes before scheduling the next call.\n *\n * Unlike the non-async Throttler, this async version supports returning values from the throttled function,\n * making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call\n * instead of setting the result on a state variable from within the throttled function.\n *\n * Error Handling:\n * - If an `onError` handler is provided, it will be called with the error and throttler instance\n * - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown\n * - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed\n * - Both onError and throwOnError can be used together - the handler will be called before any error is thrown\n * - The error state can be checked using the underlying AsyncThrottler instance\n *\n * @example\n * ```ts\n * const throttled = asyncThrottle(async (value: string) => {\n * const result = await saveToAPI(value);\n * return result; // Return value is preserved\n * }, {\n * wait: 1000,\n * onError: (error) => {\n * console.error('API call failed:', error);\n * }\n * });\n *\n * // This will execute at most once per second\n * // Returns the API response directly\n * const result = await throttled(inputElement.value);\n * ```\n */\nexport function asyncThrottle<TFn extends AnyAsyncFunction>(\n fn: TFn,\n initialOptions: AsyncThrottlerOptions<TFn>,\n) {\n const asyncThrottler = new AsyncThrottler(fn, initialOptions)\n return asyncThrottler.maybeExecute.bind(asyncThrottler)\n}\n"],"names":["parseFunctionOrValue"],"mappings":"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"}
|
|
@@ -15,7 +15,9 @@ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
|
15
15
|
*/
|
|
16
16
|
leading?: boolean;
|
|
17
17
|
/**
|
|
18
|
-
* Optional error handler for when the throttled function throws
|
|
18
|
+
* Optional error handler for when the throttled function throws.
|
|
19
|
+
* If provided, the handler will be called with the error and throttler instance.
|
|
20
|
+
* This can be used alongside throwOnError - the handler will be called before any error is thrown.
|
|
19
21
|
*/
|
|
20
22
|
onError?: (error: unknown, asyncThrottler: AsyncThrottler<TFn>) => void;
|
|
21
23
|
/**
|
|
@@ -26,6 +28,12 @@ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
|
26
28
|
* Optional function to call when the throttled function is executed
|
|
27
29
|
*/
|
|
28
30
|
onSuccess?: (result: ReturnType<TFn>, asyncThrottler: AsyncThrottler<TFn>) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to throw errors when they occur.
|
|
33
|
+
* Defaults to true if no onError handler is provided, false if an onError handler is provided.
|
|
34
|
+
* Can be explicitly set to override these defaults.
|
|
35
|
+
*/
|
|
36
|
+
throwOnError?: boolean;
|
|
29
37
|
/**
|
|
30
38
|
* Whether to execute the function on the trailing edge of the wait period
|
|
31
39
|
* Defaults to true
|
|
@@ -52,12 +60,24 @@ export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
|
52
60
|
* This is useful for rate-limiting API calls, handling scroll/resize events, or any scenario where you want to
|
|
53
61
|
* ensure a maximum execution frequency.
|
|
54
62
|
*
|
|
63
|
+
* Error Handling:
|
|
64
|
+
* - If an `onError` handler is provided, it will be called with the error and throttler instance
|
|
65
|
+
* - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
|
|
66
|
+
* - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
|
|
67
|
+
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
68
|
+
* - The error state can be checked using the underlying AsyncThrottler instance
|
|
69
|
+
*
|
|
55
70
|
* @example
|
|
56
71
|
* ```ts
|
|
57
72
|
* const throttler = new AsyncThrottler(async (value: string) => {
|
|
58
73
|
* const result = await saveToAPI(value);
|
|
59
74
|
* return result; // Return value is preserved
|
|
60
|
-
* }, {
|
|
75
|
+
* }, {
|
|
76
|
+
* wait: 1000,
|
|
77
|
+
* onError: (error) => {
|
|
78
|
+
* console.error('API call failed:', error);
|
|
79
|
+
* }
|
|
80
|
+
* });
|
|
61
81
|
*
|
|
62
82
|
* // Will only execute once per second no matter how often called
|
|
63
83
|
* // Returns the API response directly
|
|
@@ -77,16 +97,16 @@ export declare class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
77
97
|
private _settleCount;
|
|
78
98
|
private _successCount;
|
|
79
99
|
private _timeoutId;
|
|
100
|
+
private _resolvePreviousPromise;
|
|
80
101
|
constructor(fn: TFn, initialOptions: AsyncThrottlerOptions<TFn>);
|
|
81
102
|
/**
|
|
82
103
|
* Updates the throttler options
|
|
83
|
-
* Returns the new options state
|
|
84
104
|
*/
|
|
85
105
|
setOptions(newOptions: Partial<AsyncThrottlerOptions<TFn>>): void;
|
|
86
106
|
/**
|
|
87
107
|
* Returns the current options
|
|
88
108
|
*/
|
|
89
|
-
getOptions():
|
|
109
|
+
getOptions(): AsyncThrottlerOptions<TFn>;
|
|
90
110
|
/**
|
|
91
111
|
* Returns the current enabled state of the throttler
|
|
92
112
|
*/
|
|
@@ -96,11 +116,22 @@ export declare class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
96
116
|
*/
|
|
97
117
|
getWait(): number;
|
|
98
118
|
/**
|
|
99
|
-
* Attempts to execute the throttled function
|
|
100
|
-
* If a call is already in progress, it may be blocked or queued depending on the `wait` option
|
|
119
|
+
* Attempts to execute the throttled function.
|
|
120
|
+
* If a call is already in progress, it may be blocked or queued depending on the `wait` option.
|
|
121
|
+
*
|
|
122
|
+
* Error Handling:
|
|
123
|
+
* - If the throttled function throws and no `onError` handler is configured,
|
|
124
|
+
* the error will be thrown from this method.
|
|
125
|
+
* - If an `onError` handler is configured, errors will be caught and passed to the handler,
|
|
126
|
+
* and this method will return undefined.
|
|
127
|
+
* - The error state can be checked using `getErrorCount()` and `getIsExecuting()`.
|
|
128
|
+
*
|
|
129
|
+
* @returns A promise that resolves with the function's return value, or undefined if an error occurred and was handled by onError
|
|
130
|
+
* @throws The error from the throttled function if no onError handler is configured
|
|
101
131
|
*/
|
|
102
132
|
maybeExecute(...args: Parameters<TFn>): Promise<ReturnType<TFn> | undefined>;
|
|
103
|
-
private
|
|
133
|
+
private execute;
|
|
134
|
+
private resolvePreviousPromise;
|
|
104
135
|
/**
|
|
105
136
|
* Cancels any pending execution or aborts any execution in progress
|
|
106
137
|
*/
|
|
@@ -147,12 +178,24 @@ export declare class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
147
178
|
* making it ideal for API calls and other async operations where you want the result of the `maybeExecute` call
|
|
148
179
|
* instead of setting the result on a state variable from within the throttled function.
|
|
149
180
|
*
|
|
181
|
+
* Error Handling:
|
|
182
|
+
* - If an `onError` handler is provided, it will be called with the error and throttler instance
|
|
183
|
+
* - If `throwOnError` is true (default when no onError handler is provided), the error will be thrown
|
|
184
|
+
* - If `throwOnError` is false (default when onError handler is provided), the error will be swallowed
|
|
185
|
+
* - Both onError and throwOnError can be used together - the handler will be called before any error is thrown
|
|
186
|
+
* - The error state can be checked using the underlying AsyncThrottler instance
|
|
187
|
+
*
|
|
150
188
|
* @example
|
|
151
189
|
* ```ts
|
|
152
190
|
* const throttled = asyncThrottle(async (value: string) => {
|
|
153
191
|
* const result = await saveToAPI(value);
|
|
154
192
|
* return result; // Return value is preserved
|
|
155
|
-
* }, {
|
|
193
|
+
* }, {
|
|
194
|
+
* wait: 1000,
|
|
195
|
+
* onError: (error) => {
|
|
196
|
+
* console.error('API call failed:', error);
|
|
197
|
+
* }
|
|
198
|
+
* });
|
|
156
199
|
*
|
|
157
200
|
* // This will execute at most once per second
|
|
158
201
|
* // Returns the API response directly
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const defaultOptions = {
|
|
4
|
+
getShouldExecute: () => false,
|
|
5
|
+
maxSize: Infinity,
|
|
6
|
+
started: true,
|
|
7
|
+
wait: Infinity
|
|
8
|
+
};
|
|
9
|
+
class Batcher {
|
|
10
|
+
constructor(fn, initialOptions) {
|
|
11
|
+
this.fn = fn;
|
|
12
|
+
this._batchExecutionCount = 0;
|
|
13
|
+
this._itemExecutionCount = 0;
|
|
14
|
+
this._items = [];
|
|
15
|
+
this._timeoutId = null;
|
|
16
|
+
this._options = { ...defaultOptions, ...initialOptions };
|
|
17
|
+
this._running = this._options.started;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Updates the batcher options
|
|
21
|
+
*/
|
|
22
|
+
setOptions(newOptions) {
|
|
23
|
+
this._options = { ...this._options, ...newOptions };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns the current batcher options
|
|
27
|
+
*/
|
|
28
|
+
getOptions() {
|
|
29
|
+
return this._options;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Adds an item to the batcher
|
|
33
|
+
* If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed
|
|
34
|
+
*/
|
|
35
|
+
addItem(item) {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
this._items.push(item);
|
|
38
|
+
(_b = (_a = this._options).onItemsChange) == null ? void 0 : _b.call(_a, this);
|
|
39
|
+
const shouldProcess = this._items.length >= this._options.maxSize || this._options.getShouldExecute(this._items, this);
|
|
40
|
+
if (shouldProcess) {
|
|
41
|
+
this.execute();
|
|
42
|
+
} else if (this._running && !this._timeoutId && this._options.wait !== Infinity) {
|
|
43
|
+
this._timeoutId = setTimeout(() => this.execute(), this._options.wait);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Processes the current batch of items.
|
|
48
|
+
* This method will automatically be triggered if the batcher is running and any of these conditions are met:
|
|
49
|
+
* - The number of items reaches batchSize
|
|
50
|
+
* - The wait duration has elapsed
|
|
51
|
+
* - The getShouldExecute function returns true upon adding an item
|
|
52
|
+
*
|
|
53
|
+
* You can also call this method manually to process the current batch at any time.
|
|
54
|
+
*/
|
|
55
|
+
execute() {
|
|
56
|
+
var _a, _b, _c, _d;
|
|
57
|
+
if (this._timeoutId) {
|
|
58
|
+
clearTimeout(this._timeoutId);
|
|
59
|
+
this._timeoutId = null;
|
|
60
|
+
}
|
|
61
|
+
if (this._items.length === 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const batch2 = this.getAllItems();
|
|
65
|
+
this._items = [];
|
|
66
|
+
(_b = (_a = this._options).onItemsChange) == null ? void 0 : _b.call(_a, this);
|
|
67
|
+
this.fn(batch2);
|
|
68
|
+
this._batchExecutionCount++;
|
|
69
|
+
this._itemExecutionCount += batch2.length;
|
|
70
|
+
(_d = (_c = this._options).onExecute) == null ? void 0 : _d.call(_c, this);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Stops the batcher from processing batches
|
|
74
|
+
*/
|
|
75
|
+
stop() {
|
|
76
|
+
var _a, _b;
|
|
77
|
+
this._running = false;
|
|
78
|
+
(_b = (_a = this._options).onIsRunningChange) == null ? void 0 : _b.call(_a, this);
|
|
79
|
+
if (this._timeoutId) {
|
|
80
|
+
clearTimeout(this._timeoutId);
|
|
81
|
+
this._timeoutId = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Starts the batcher and processes any pending items
|
|
86
|
+
*/
|
|
87
|
+
start() {
|
|
88
|
+
var _a, _b;
|
|
89
|
+
this._running = true;
|
|
90
|
+
(_b = (_a = this._options).onIsRunningChange) == null ? void 0 : _b.call(_a, this);
|
|
91
|
+
if (this._items.length > 0 && !this._timeoutId) {
|
|
92
|
+
this._timeoutId = setTimeout(() => this.execute(), this._options.wait);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns the current number of items in the batcher
|
|
97
|
+
*/
|
|
98
|
+
getSize() {
|
|
99
|
+
return this._items.length;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns true if the batcher is empty
|
|
103
|
+
*/
|
|
104
|
+
getIsEmpty() {
|
|
105
|
+
return this._items.length === 0;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns true if the batcher is running
|
|
109
|
+
*/
|
|
110
|
+
getIsRunning() {
|
|
111
|
+
return this._running;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Returns a copy of all items currently in the batcher
|
|
115
|
+
*/
|
|
116
|
+
getAllItems() {
|
|
117
|
+
return [...this._items];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Returns the number of times batches have been processed
|
|
121
|
+
*/
|
|
122
|
+
getBatchExecutionCount() {
|
|
123
|
+
return this._batchExecutionCount;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Returns the total number of individual items that have been processed
|
|
127
|
+
*/
|
|
128
|
+
getItemExecutionCount() {
|
|
129
|
+
return this._itemExecutionCount;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function batch(fn, options) {
|
|
133
|
+
const batcher = new Batcher(fn, options);
|
|
134
|
+
return batcher.addItem.bind(batcher);
|
|
135
|
+
}
|
|
136
|
+
exports.Batcher = Batcher;
|
|
137
|
+
exports.batch = batch;
|
|
138
|
+
//# sourceMappingURL=batcher.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batcher.cjs","sources":["../../src/batcher.ts"],"sourcesContent":["import type { OptionalKeys } from './types'\n\n/**\n * Options for configuring a Batcher instance\n */\nexport interface BatcherOptions<TValue> {\n /**\n * Custom function to determine if a batch should be processed\n * Return true to process the batch immediately\n */\n getShouldExecute?: (items: Array<TValue>, batcher: Batcher<TValue>) => boolean\n /**\n * Maximum number of items in a batch\n * @default Infinity\n */\n maxSize?: number\n /**\n * Callback fired after a batch is processed\n */\n onExecute?: (batcher: Batcher<TValue>) => void\n /**\n * Callback fired when the batcher's running state changes\n */\n onIsRunningChange?: (batcher: Batcher<TValue>) => void\n /**\n * Callback fired after items are added to the batcher\n */\n onItemsChange?: (batcher: Batcher<TValue>) => void\n /**\n * Whether the batcher should start processing immediately\n * @default true\n */\n started?: boolean\n /**\n * Maximum time in milliseconds to wait before processing a batch.\n * If the wait duration has elapsed, the batch will be processed.\n * If not provided, the batch will not be triggered by a timeout.\n * @default Infinity\n */\n wait?: number\n}\n\ntype BatcherOptionsWithOptionalCallbacks<TValue> = OptionalKeys<\n Required<BatcherOptions<TValue>>,\n 'onExecute' | 'onItemsChange' | 'onIsRunningChange'\n>\n\nconst defaultOptions: BatcherOptionsWithOptionalCallbacks<any> = {\n getShouldExecute: () => false,\n maxSize: Infinity,\n started: true,\n wait: Infinity,\n}\n\n/**\n * A class that collects items and processes them in batches.\n *\n * Batching is a technique for grouping multiple operations together to be processed as a single unit.\n *\n * The Batcher provides a flexible way to implement batching with configurable:\n * - Maximum batch size (number of items per batch)\n * - Time-based batching (process after X milliseconds)\n * - Custom batch processing logic via getShouldExecute\n * - Event callbacks for monitoring batch operations\n *\n * @example\n * ```ts\n * const batcher = new Batcher<number>(\n * (items) => console.log('Processing batch:', items),\n * {\n * maxSize: 5,\n * wait: 2000,\n * onExecuteBatch: (items) => console.log('Batch executed:', items)\n * }\n * );\n *\n * batcher.addItem(1);\n * batcher.addItem(2);\n * // After 2 seconds or when 5 items are added, whichever comes first,\n * // the batch will be processed\n * // batcher.execute() // manually trigger a batch\n * ```\n */\nexport class Batcher<TValue> {\n private _options: BatcherOptionsWithOptionalCallbacks<TValue>\n private _batchExecutionCount = 0\n private _itemExecutionCount = 0\n private _items: Array<TValue> = []\n private _running: boolean\n private _timeoutId: NodeJS.Timeout | null = null\n\n constructor(\n private fn: (items: Array<TValue>) => void,\n initialOptions: BatcherOptions<TValue>,\n ) {\n this._options = { ...defaultOptions, ...initialOptions }\n this._running = this._options.started\n }\n\n /**\n * Updates the batcher options\n */\n setOptions(newOptions: Partial<BatcherOptions<TValue>>): void {\n this._options = { ...this._options, ...newOptions }\n }\n\n /**\n * Returns the current batcher options\n */\n getOptions(): BatcherOptions<TValue> {\n return this._options\n }\n\n /**\n * Adds an item to the batcher\n * If the batch size is reached, timeout occurs, or shouldProcess returns true, the batch will be processed\n */\n addItem(item: TValue): void {\n this._items.push(item)\n this._options.onItemsChange?.(this)\n\n const shouldProcess =\n this._items.length >= this._options.maxSize ||\n this._options.getShouldExecute(this._items, this)\n\n if (shouldProcess) {\n this.execute()\n } else if (\n this._running &&\n !this._timeoutId &&\n this._options.wait !== Infinity\n ) {\n this._timeoutId = setTimeout(() => this.execute(), this._options.wait)\n }\n }\n\n /**\n * Processes the current batch of items.\n * This method will automatically be triggered if the batcher is running and any of these conditions are met:\n * - The number of items reaches batchSize\n * - The wait duration has elapsed\n * - The getShouldExecute function returns true upon adding an item\n *\n * You can also call this method manually to process the current batch at any time.\n */\n execute(): void {\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n this._timeoutId = null\n }\n\n if (this._items.length === 0) {\n return\n }\n\n const batch = this.getAllItems() // copy of the items to be processed (to prevent race conditions)\n this._items = [] // Clear items before processing to prevent race conditions\n this._options.onItemsChange?.(this) // Call onItemsChange to notify listeners that the items have changed\n\n this.fn(batch)\n this._batchExecutionCount++\n this._itemExecutionCount += batch.length\n this._options.onExecute?.(this)\n }\n\n /**\n * Stops the batcher from processing batches\n */\n stop(): void {\n this._running = false\n this._options.onIsRunningChange?.(this)\n if (this._timeoutId) {\n clearTimeout(this._timeoutId)\n this._timeoutId = null\n }\n }\n\n /**\n * Starts the batcher and processes any pending items\n */\n start(): void {\n this._running = true\n this._options.onIsRunningChange?.(this)\n if (this._items.length > 0 && !this._timeoutId) {\n this._timeoutId = setTimeout(() => this.execute(), this._options.wait)\n }\n }\n\n /**\n * Returns the current number of items in the batcher\n */\n getSize(): number {\n return this._items.length\n }\n\n /**\n * Returns true if the batcher is empty\n */\n getIsEmpty(): boolean {\n return this._items.length === 0\n }\n\n /**\n * Returns true if the batcher is running\n */\n getIsRunning(): boolean {\n return this._running\n }\n\n /**\n * Returns a copy of all items currently in the batcher\n */\n getAllItems(): Array<TValue> {\n return [...this._items]\n }\n\n /**\n * Returns the number of times batches have been processed\n */\n getBatchExecutionCount(): number {\n return this._batchExecutionCount\n }\n\n /**\n * Returns the total number of individual items that have been processed\n */\n getItemExecutionCount(): number {\n return this._itemExecutionCount\n }\n}\n\n/**\n * Creates a batcher that processes items in batches\n *\n * @example\n * ```ts\n * const batchItems = batch<number>({\n * batchSize: 3,\n * processBatch: (items) => console.log('Processing:', items)\n * });\n *\n * batchItems(1);\n * batchItems(2);\n * batchItems(3); // Triggers batch processing\n * ```\n */\nexport function batch<TValue>(\n fn: (items: Array<TValue>) => void,\n options: BatcherOptions<TValue>,\n) {\n const batcher = new Batcher<TValue>(fn, options)\n return batcher.addItem.bind(batcher)\n}\n"],"names":["batch"],"mappings":";;AA+CA,MAAM,iBAA2D;AAAA,EAC/D,kBAAkB,MAAM;AAAA,EACxB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM;AACR;AA+BO,MAAM,QAAgB;AAAA,EAQ3B,YACU,IACR,gBACA;AAFQ,SAAA,KAAA;AAPV,SAAQ,uBAAuB;AAC/B,SAAQ,sBAAsB;AAC9B,SAAQ,SAAwB,CAAC;AAEjC,SAAQ,aAAoC;AAM1C,SAAK,WAAW,EAAE,GAAG,gBAAgB,GAAG,eAAe;AAClD,SAAA,WAAW,KAAK,SAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMhC,WAAW,YAAmD;AAC5D,SAAK,WAAW,EAAE,GAAG,KAAK,UAAU,GAAG,WAAW;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMpD,aAAqC;AACnC,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,QAAQ,MAAoB;;AACrB,SAAA,OAAO,KAAK,IAAI;AAChB,qBAAA,UAAS,kBAAT,4BAAyB;AAE9B,UAAM,gBACJ,KAAK,OAAO,UAAU,KAAK,SAAS,WACpC,KAAK,SAAS,iBAAiB,KAAK,QAAQ,IAAI;AAElD,QAAI,eAAe;AACjB,WAAK,QAAQ;AAAA,IAAA,WAEb,KAAK,YACL,CAAC,KAAK,cACN,KAAK,SAAS,SAAS,UACvB;AACK,WAAA,aAAa,WAAW,MAAM,KAAK,WAAW,KAAK,SAAS,IAAI;AAAA,IAAA;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYF,UAAgB;;AACd,QAAI,KAAK,YAAY;AACnB,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IAAA;AAGhB,QAAA,KAAK,OAAO,WAAW,GAAG;AAC5B;AAAA,IAAA;AAGIA,UAAAA,SAAQ,KAAK,YAAY;AAC/B,SAAK,SAAS,CAAC;AACV,qBAAA,UAAS,kBAAT,4BAAyB;AAE9B,SAAK,GAAGA,MAAK;AACR,SAAA;AACL,SAAK,uBAAuBA,OAAM;AAC7B,qBAAA,UAAS,cAAT,4BAAqB;AAAA,EAAI;AAAA;AAAA;AAAA;AAAA,EAMhC,OAAa;;AACX,SAAK,WAAW;AACX,qBAAA,UAAS,sBAAT,4BAA6B;AAClC,QAAI,KAAK,YAAY;AACnB,mBAAa,KAAK,UAAU;AAC5B,WAAK,aAAa;AAAA,IAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAMF,QAAc;;AACZ,SAAK,WAAW;AACX,qBAAA,UAAS,sBAAT,4BAA6B;AAClC,QAAI,KAAK,OAAO,SAAS,KAAK,CAAC,KAAK,YAAY;AACzC,WAAA,aAAa,WAAW,MAAM,KAAK,WAAW,KAAK,SAAS,IAAI;AAAA,IAAA;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA,EAMF,UAAkB;AAChB,WAAO,KAAK,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMrB,aAAsB;AACb,WAAA,KAAK,OAAO,WAAW;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMhC,eAAwB;AACtB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,cAA6B;AACpB,WAAA,CAAC,GAAG,KAAK,MAAM;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMxB,yBAAiC;AAC/B,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,wBAAgC;AAC9B,WAAO,KAAK;AAAA,EAAA;AAEhB;AAiBgB,SAAA,MACd,IACA,SACA;AACA,QAAM,UAAU,IAAI,QAAgB,IAAI,OAAO;AACxC,SAAA,QAAQ,QAAQ,KAAK,OAAO;AACrC;;;"}
|