@tanstack/pacer 0.21.1 → 0.23.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/README.md +21 -6
- package/dist/async-batcher.d.ts +11 -14
- package/dist/async-batcher.js +186 -132
- package/dist/async-debouncer.d.ts +10 -13
- package/dist/async-debouncer.js +187 -146
- package/dist/async-queuer.d.ts +12 -14
- package/dist/async-queuer.js +384 -259
- package/dist/async-rate-limiter.d.ts +9 -12
- package/dist/async-rate-limiter.js +198 -147
- package/dist/async-retryer.d.ts +10 -10
- package/dist/async-retryer.js +209 -188
- package/dist/async-throttler.d.ts +10 -13
- package/dist/async-throttler.js +215 -161
- package/dist/batcher.d.ts +5 -8
- package/dist/batcher.js +107 -87
- package/dist/debouncer.d.ts +6 -9
- package/dist/debouncer.js +99 -86
- package/dist/event-client.d.ts +8 -11
- package/dist/event-client.js +1 -2
- package/dist/index.js +1 -1
- package/dist/queuer.d.ts +8 -10
- package/dist/queuer.js +277 -207
- package/dist/rate-limiter.d.ts +6 -9
- package/dist/rate-limiter.js +127 -109
- package/dist/throttler.d.ts +6 -9
- package/dist/throttler.js +127 -90
- package/dist/types.d.ts +4 -6
- package/dist/utils.d.ts +3 -6
- package/dist/utils.js +1 -2
- package/package.json +23 -70
- package/dist/async-batcher.cjs +0 -337
- package/dist/async-batcher.cjs.map +0 -1
- package/dist/async-batcher.d.cts +0 -344
- package/dist/async-batcher.js.map +0 -1
- package/dist/async-debouncer.cjs +0 -330
- package/dist/async-debouncer.cjs.map +0 -1
- package/dist/async-debouncer.d.cts +0 -300
- package/dist/async-debouncer.js.map +0 -1
- package/dist/async-queuer.cjs +0 -484
- package/dist/async-queuer.cjs.map +0 -1
- package/dist/async-queuer.d.cts +0 -440
- package/dist/async-queuer.js.map +0 -1
- package/dist/async-rate-limiter.cjs +0 -373
- package/dist/async-rate-limiter.cjs.map +0 -1
- package/dist/async-rate-limiter.d.cts +0 -357
- package/dist/async-rate-limiter.js.map +0 -1
- package/dist/async-retryer.cjs +0 -374
- package/dist/async-retryer.cjs.map +0 -1
- package/dist/async-retryer.d.cts +0 -319
- package/dist/async-retryer.js.map +0 -1
- package/dist/async-throttler.cjs +0 -347
- package/dist/async-throttler.cjs.map +0 -1
- package/dist/async-throttler.d.cts +0 -320
- package/dist/async-throttler.js.map +0 -1
- package/dist/batcher.cjs +0 -200
- package/dist/batcher.cjs.map +0 -1
- package/dist/batcher.d.cts +0 -180
- package/dist/batcher.js.map +0 -1
- package/dist/debouncer.cjs +0 -203
- package/dist/debouncer.cjs.map +0 -1
- package/dist/debouncer.d.cts +0 -167
- package/dist/debouncer.js.map +0 -1
- package/dist/event-client.cjs +0 -64
- package/dist/event-client.cjs.map +0 -1
- package/dist/event-client.d.cts +0 -66
- package/dist/event-client.js.map +0 -1
- package/dist/index.cjs +0 -52
- package/dist/index.d.cts +0 -15
- package/dist/queuer.cjs +0 -401
- package/dist/queuer.cjs.map +0 -1
- package/dist/queuer.d.cts +0 -345
- package/dist/queuer.js.map +0 -1
- package/dist/rate-limiter.cjs +0 -263
- package/dist/rate-limiter.cjs.map +0 -1
- package/dist/rate-limiter.d.cts +0 -215
- package/dist/rate-limiter.js.map +0 -1
- package/dist/throttler.cjs +0 -215
- package/dist/throttler.cjs.map +0 -1
- package/dist/throttler.d.cts +0 -207
- package/dist/throttler.js.map +0 -1
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +0 -13
- package/dist/utils.cjs +0 -14
- package/dist/utils.cjs.map +0 -1
- package/dist/utils.d.cts +0 -8
- package/dist/utils.js.map +0 -1
- package/src/async-batcher.ts +0 -594
- package/src/async-debouncer.ts +0 -565
- package/src/async-queuer.ts +0 -925
- package/src/async-rate-limiter.ts +0 -647
- package/src/async-retryer.ts +0 -684
- package/src/async-throttler.ts +0 -633
- package/src/batcher.ts +0 -329
- package/src/debouncer.ts +0 -334
- package/src/event-client.ts +0 -129
- package/src/index.ts +0 -24
- package/src/queuer.ts +0 -740
- package/src/rate-limiter.ts +0 -429
- package/src/throttler.ts +0 -380
- package/src/types.ts +0 -12
- package/src/utils.ts +0 -12
package/dist/async-retryer.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { parseFunctionOrValue } from "./utils.js";
|
|
2
|
-
import { emitChange, pacerEventClient } from "./event-client.js";
|
|
3
2
|
import { Store } from "@tanstack/store";
|
|
4
3
|
|
|
5
4
|
//#region src/async-retryer.ts
|
|
@@ -131,7 +130,11 @@ const defaultOptions = {
|
|
|
131
130
|
* @template TFn The async function type to be retried.
|
|
132
131
|
*/
|
|
133
132
|
var AsyncRetryer = class {
|
|
134
|
-
|
|
133
|
+
fn;
|
|
134
|
+
store = new Store(getDefaultAsyncRetryerState());
|
|
135
|
+
key;
|
|
136
|
+
options;
|
|
137
|
+
#abortController = null;
|
|
135
138
|
/**
|
|
136
139
|
* Creates a new AsyncRetryer instance
|
|
137
140
|
* @param fn The async function to retry
|
|
@@ -139,178 +142,6 @@ var AsyncRetryer = class {
|
|
|
139
142
|
*/
|
|
140
143
|
constructor(fn, initialOptions = {}) {
|
|
141
144
|
this.fn = fn;
|
|
142
|
-
this.store = new Store(getDefaultAsyncRetryerState());
|
|
143
|
-
this.#abortController = null;
|
|
144
|
-
this.setOptions = (newOptions) => {
|
|
145
|
-
this.options = {
|
|
146
|
-
...this.options,
|
|
147
|
-
...newOptions
|
|
148
|
-
};
|
|
149
|
-
};
|
|
150
|
-
this.#setState = (newState) => {
|
|
151
|
-
this.store.setState((state) => {
|
|
152
|
-
const combinedState = {
|
|
153
|
-
...state,
|
|
154
|
-
...newState
|
|
155
|
-
};
|
|
156
|
-
const { isExecuting, currentAttempt } = combinedState;
|
|
157
|
-
return {
|
|
158
|
-
...combinedState,
|
|
159
|
-
status: !this.#getEnabled() ? "disabled" : isExecuting && currentAttempt === 1 ? "executing" : isExecuting && currentAttempt > 1 ? "retrying" : "idle"
|
|
160
|
-
};
|
|
161
|
-
});
|
|
162
|
-
emitChange("AsyncRetryer", this);
|
|
163
|
-
};
|
|
164
|
-
this.#getEnabled = () => {
|
|
165
|
-
return !!parseFunctionOrValue(this.options.enabled, this);
|
|
166
|
-
};
|
|
167
|
-
this.#getMaxAttempts = () => {
|
|
168
|
-
return parseFunctionOrValue(this.options.maxAttempts, this);
|
|
169
|
-
};
|
|
170
|
-
this.#getBaseWait = () => {
|
|
171
|
-
return parseFunctionOrValue(this.options.baseWait, this);
|
|
172
|
-
};
|
|
173
|
-
this.#getMaxWait = () => {
|
|
174
|
-
return parseFunctionOrValue(this.options.maxWait, this);
|
|
175
|
-
};
|
|
176
|
-
this.#calculateJitter = (waitTime) => {
|
|
177
|
-
const jitterAmount = this.options.jitter;
|
|
178
|
-
if (jitterAmount <= 0) return 0;
|
|
179
|
-
try {
|
|
180
|
-
const crypto = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
|
|
181
|
-
if (crypto?.getRandomValues) {
|
|
182
|
-
const array = new Uint32Array(1);
|
|
183
|
-
crypto.getRandomValues(array);
|
|
184
|
-
const randomFactor = array[0] / 4294967295 * 2 - 1;
|
|
185
|
-
return Math.floor(waitTime * jitterAmount * randomFactor);
|
|
186
|
-
}
|
|
187
|
-
} catch {}
|
|
188
|
-
return 0;
|
|
189
|
-
};
|
|
190
|
-
this.#calculateWait = (attempt) => {
|
|
191
|
-
const baseWait = this.#getBaseWait();
|
|
192
|
-
let waitTime;
|
|
193
|
-
switch (this.options.backoff) {
|
|
194
|
-
case "linear":
|
|
195
|
-
waitTime = baseWait * attempt;
|
|
196
|
-
break;
|
|
197
|
-
case "exponential":
|
|
198
|
-
waitTime = baseWait * Math.pow(2, attempt - 1);
|
|
199
|
-
break;
|
|
200
|
-
default:
|
|
201
|
-
waitTime = baseWait;
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
waitTime = Math.min(waitTime, this.#getMaxWait());
|
|
205
|
-
const jitter = this.#calculateJitter(waitTime);
|
|
206
|
-
return Math.max(0, waitTime + jitter);
|
|
207
|
-
};
|
|
208
|
-
this.execute = async (...args) => {
|
|
209
|
-
if (!this.#getEnabled()) return;
|
|
210
|
-
this.abort("new-execution");
|
|
211
|
-
const startTime = Date.now();
|
|
212
|
-
let lastError;
|
|
213
|
-
let result;
|
|
214
|
-
this.#abortController = new AbortController();
|
|
215
|
-
const signal = this.#abortController.signal;
|
|
216
|
-
this.#setState({
|
|
217
|
-
isExecuting: true,
|
|
218
|
-
currentAttempt: 0,
|
|
219
|
-
lastError: void 0
|
|
220
|
-
});
|
|
221
|
-
let totalTimeoutId;
|
|
222
|
-
if (this.options.maxTotalExecutionTime !== Infinity) totalTimeoutId = setTimeout(() => {
|
|
223
|
-
this.options.onTotalExecutionTimeout?.(this);
|
|
224
|
-
this.abort("total-timeout");
|
|
225
|
-
}, this.options.maxTotalExecutionTime);
|
|
226
|
-
let isLastAttempt = false;
|
|
227
|
-
for (let attempt = 1; attempt <= this.#getMaxAttempts(); attempt++) {
|
|
228
|
-
isLastAttempt = attempt === this.#getMaxAttempts();
|
|
229
|
-
this.#setState({ currentAttempt: attempt });
|
|
230
|
-
try {
|
|
231
|
-
if (signal.aborted) return;
|
|
232
|
-
const currentTotalTime = Date.now() - startTime;
|
|
233
|
-
if (this.options.maxTotalExecutionTime !== Infinity && currentTotalTime >= this.options.maxTotalExecutionTime) {
|
|
234
|
-
this.options.onTotalExecutionTimeout?.(this);
|
|
235
|
-
this.abort("total-timeout");
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if (this.options.maxExecutionTime === Infinity) result = await this.fn(...args);
|
|
239
|
-
else result = await Promise.race([this.fn(...args), new Promise((_, reject) => {
|
|
240
|
-
const timeout = setTimeout(() => {
|
|
241
|
-
this.options.onExecutionTimeout?.(this);
|
|
242
|
-
this.abort("execution-timeout");
|
|
243
|
-
reject(/* @__PURE__ */ new Error(`Execution timeout: ${this.options.maxExecutionTime}ms exceeded`));
|
|
244
|
-
}, this.options.maxExecutionTime);
|
|
245
|
-
signal.addEventListener("abort", () => {
|
|
246
|
-
clearTimeout(timeout);
|
|
247
|
-
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
248
|
-
}, { once: true });
|
|
249
|
-
})]);
|
|
250
|
-
if (signal.aborted) return;
|
|
251
|
-
const totalTime = Date.now() - startTime;
|
|
252
|
-
this.#setState({
|
|
253
|
-
executionCount: this.store.state.executionCount + 1,
|
|
254
|
-
isExecuting: false,
|
|
255
|
-
lastExecutionTime: Date.now(),
|
|
256
|
-
totalExecutionTime: totalTime,
|
|
257
|
-
currentAttempt: 0,
|
|
258
|
-
lastResult: result
|
|
259
|
-
});
|
|
260
|
-
this.options.onSuccess?.(result, args, this);
|
|
261
|
-
return result;
|
|
262
|
-
} catch (error) {
|
|
263
|
-
if (error && typeof error === "object" && "name" in error && error.name === "AbortError") return;
|
|
264
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
265
|
-
this.#setState({ lastError });
|
|
266
|
-
this.options.onError?.(lastError, args, this);
|
|
267
|
-
if (attempt < this.#getMaxAttempts()) {
|
|
268
|
-
this.options.onRetry?.(attempt, lastError, this);
|
|
269
|
-
const wait = this.#calculateWait(attempt);
|
|
270
|
-
if (wait > 0) {
|
|
271
|
-
this.#setState({
|
|
272
|
-
isExecuting: true,
|
|
273
|
-
currentAttempt: attempt + 1
|
|
274
|
-
});
|
|
275
|
-
await new Promise((resolve) => {
|
|
276
|
-
const timeout = setTimeout(() => {
|
|
277
|
-
signal.removeEventListener("abort", onAbort);
|
|
278
|
-
resolve();
|
|
279
|
-
}, wait);
|
|
280
|
-
const onAbort = () => {
|
|
281
|
-
clearTimeout(timeout);
|
|
282
|
-
signal.removeEventListener("abort", onAbort);
|
|
283
|
-
resolve();
|
|
284
|
-
};
|
|
285
|
-
signal.addEventListener("abort", onAbort);
|
|
286
|
-
});
|
|
287
|
-
if (signal.aborted) return;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} finally {
|
|
291
|
-
this.options.onSettled?.(args, this);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (totalTimeoutId) clearTimeout(totalTimeoutId);
|
|
295
|
-
this.#setState({ isExecuting: false });
|
|
296
|
-
this.options.onLastError?.(lastError, this);
|
|
297
|
-
this.options.onSettled?.(args, this);
|
|
298
|
-
if (this.options.throwOnError === "last" && isLastAttempt || this.options.throwOnError === true) throw lastError;
|
|
299
|
-
};
|
|
300
|
-
this.getAbortSignal = () => {
|
|
301
|
-
return this.#abortController?.signal ?? null;
|
|
302
|
-
};
|
|
303
|
-
this.abort = (reason = "manual") => {
|
|
304
|
-
if (this.#abortController) {
|
|
305
|
-
this.#abortController.abort();
|
|
306
|
-
this.#abortController = null;
|
|
307
|
-
this.#setState({ isExecuting: false });
|
|
308
|
-
this.options.onAbort?.(reason, this);
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
this.reset = () => {
|
|
312
|
-
this.#setState(getDefaultAsyncRetryerState());
|
|
313
|
-
};
|
|
314
145
|
this.key = initialOptions.key;
|
|
315
146
|
this.options = {
|
|
316
147
|
...defaultOptions,
|
|
@@ -318,19 +149,210 @@ var AsyncRetryer = class {
|
|
|
318
149
|
throwOnError: initialOptions.throwOnError ?? (initialOptions.onError ? false : defaultOptions.throwOnError)
|
|
319
150
|
};
|
|
320
151
|
this.#setState(this.options.initialState ?? {});
|
|
321
|
-
if (this.key) pacerEventClient.on("d-AsyncRetryer", (event) => {
|
|
322
|
-
if (event.payload.key !== this.key) return;
|
|
323
|
-
this.#setState(event.payload.store.state);
|
|
324
|
-
this.setOptions(event.payload.options);
|
|
325
|
-
});
|
|
326
152
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Updates the retryer options
|
|
155
|
+
* @param newOptions Partial options to merge with existing options
|
|
156
|
+
*/
|
|
157
|
+
setOptions = (newOptions) => {
|
|
158
|
+
this.options = {
|
|
159
|
+
...this.options,
|
|
160
|
+
...newOptions
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
#setState = (newState) => {
|
|
164
|
+
this.store.setState((state) => {
|
|
165
|
+
const combinedState = {
|
|
166
|
+
...state,
|
|
167
|
+
...newState
|
|
168
|
+
};
|
|
169
|
+
const { isExecuting, currentAttempt } = combinedState;
|
|
170
|
+
return {
|
|
171
|
+
...combinedState,
|
|
172
|
+
status: !this.#getEnabled() ? "disabled" : isExecuting && currentAttempt === 1 ? "executing" : isExecuting && currentAttempt > 1 ? "retrying" : "idle"
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
#getEnabled = () => {
|
|
177
|
+
return !!parseFunctionOrValue(this.options.enabled, this);
|
|
178
|
+
};
|
|
179
|
+
#getMaxAttempts = () => {
|
|
180
|
+
return parseFunctionOrValue(this.options.maxAttempts, this);
|
|
181
|
+
};
|
|
182
|
+
#getBaseWait = () => {
|
|
183
|
+
return parseFunctionOrValue(this.options.baseWait, this);
|
|
184
|
+
};
|
|
185
|
+
#getMaxWait = () => {
|
|
186
|
+
return parseFunctionOrValue(this.options.maxWait, this);
|
|
187
|
+
};
|
|
188
|
+
#calculateJitter = (waitTime) => {
|
|
189
|
+
const jitterAmount = this.options.jitter;
|
|
190
|
+
if (jitterAmount <= 0) return 0;
|
|
191
|
+
try {
|
|
192
|
+
const crypto = typeof globalThis !== "undefined" ? globalThis.crypto : void 0;
|
|
193
|
+
if (crypto?.getRandomValues) {
|
|
194
|
+
const array = /* @__PURE__ */ new Uint32Array(1);
|
|
195
|
+
crypto.getRandomValues(array);
|
|
196
|
+
const randomFactor = array[0] / 4294967295 * 2 - 1;
|
|
197
|
+
return Math.floor(waitTime * jitterAmount * randomFactor);
|
|
198
|
+
}
|
|
199
|
+
} catch {}
|
|
200
|
+
return 0;
|
|
201
|
+
};
|
|
202
|
+
#calculateWait = (attempt) => {
|
|
203
|
+
const baseWait = this.#getBaseWait();
|
|
204
|
+
let waitTime;
|
|
205
|
+
switch (this.options.backoff) {
|
|
206
|
+
case "linear":
|
|
207
|
+
waitTime = baseWait * attempt;
|
|
208
|
+
break;
|
|
209
|
+
case "exponential":
|
|
210
|
+
waitTime = baseWait * Math.pow(2, attempt - 1);
|
|
211
|
+
break;
|
|
212
|
+
default: waitTime = baseWait;
|
|
213
|
+
}
|
|
214
|
+
waitTime = Math.min(waitTime, this.#getMaxWait());
|
|
215
|
+
const jitter = this.#calculateJitter(waitTime);
|
|
216
|
+
return Math.max(0, waitTime + jitter);
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Executes the function with retry logic
|
|
220
|
+
* @param args Arguments to pass to the function
|
|
221
|
+
* @returns The function result, or undefined if disabled or all retries failed (when throwOnError is false)
|
|
222
|
+
* @throws The last error if throwOnError is true and all retries fail
|
|
223
|
+
*/
|
|
224
|
+
execute = async (...args) => {
|
|
225
|
+
if (!this.#getEnabled()) return;
|
|
226
|
+
this.abort("new-execution");
|
|
227
|
+
const startTime = Date.now();
|
|
228
|
+
let lastError;
|
|
229
|
+
let result;
|
|
230
|
+
this.#abortController = new AbortController();
|
|
231
|
+
const signal = this.#abortController.signal;
|
|
232
|
+
this.#setState({
|
|
233
|
+
isExecuting: true,
|
|
234
|
+
currentAttempt: 0,
|
|
235
|
+
lastError: void 0
|
|
236
|
+
});
|
|
237
|
+
let totalTimeoutId;
|
|
238
|
+
if (this.options.maxTotalExecutionTime !== Infinity) totalTimeoutId = setTimeout(() => {
|
|
239
|
+
this.options.onTotalExecutionTimeout?.(this);
|
|
240
|
+
this.abort("total-timeout");
|
|
241
|
+
}, this.options.maxTotalExecutionTime);
|
|
242
|
+
let isLastAttempt = false;
|
|
243
|
+
for (let attempt = 1; attempt <= this.#getMaxAttempts(); attempt++) {
|
|
244
|
+
isLastAttempt = attempt === this.#getMaxAttempts();
|
|
245
|
+
this.#setState({ currentAttempt: attempt });
|
|
246
|
+
try {
|
|
247
|
+
if (signal.aborted) return;
|
|
248
|
+
const currentTotalTime = Date.now() - startTime;
|
|
249
|
+
if (this.options.maxTotalExecutionTime !== Infinity && currentTotalTime >= this.options.maxTotalExecutionTime) {
|
|
250
|
+
this.options.onTotalExecutionTimeout?.(this);
|
|
251
|
+
this.abort("total-timeout");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (this.options.maxExecutionTime === Infinity) result = await this.fn(...args);
|
|
255
|
+
else result = await Promise.race([this.fn(...args), new Promise((_, reject) => {
|
|
256
|
+
const timeout = setTimeout(() => {
|
|
257
|
+
this.options.onExecutionTimeout?.(this);
|
|
258
|
+
this.abort("execution-timeout");
|
|
259
|
+
reject(/* @__PURE__ */ new Error(`Execution timeout: ${this.options.maxExecutionTime}ms exceeded`));
|
|
260
|
+
}, this.options.maxExecutionTime);
|
|
261
|
+
signal.addEventListener("abort", () => {
|
|
262
|
+
clearTimeout(timeout);
|
|
263
|
+
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
264
|
+
}, { once: true });
|
|
265
|
+
})]);
|
|
266
|
+
if (signal.aborted) return;
|
|
267
|
+
const totalTime = Date.now() - startTime;
|
|
268
|
+
this.#setState({
|
|
269
|
+
executionCount: this.store.state.executionCount + 1,
|
|
270
|
+
isExecuting: false,
|
|
271
|
+
lastExecutionTime: Date.now(),
|
|
272
|
+
totalExecutionTime: totalTime,
|
|
273
|
+
currentAttempt: 0,
|
|
274
|
+
lastResult: result
|
|
275
|
+
});
|
|
276
|
+
this.options.onSuccess?.(result, args, this);
|
|
277
|
+
return result;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (error && typeof error === "object" && "name" in error && error.name === "AbortError") return;
|
|
280
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
281
|
+
this.#setState({ lastError });
|
|
282
|
+
this.options.onError?.(lastError, args, this);
|
|
283
|
+
if (attempt < this.#getMaxAttempts()) {
|
|
284
|
+
this.options.onRetry?.(attempt, lastError, this);
|
|
285
|
+
const wait = this.#calculateWait(attempt);
|
|
286
|
+
if (wait > 0) {
|
|
287
|
+
this.#setState({
|
|
288
|
+
isExecuting: true,
|
|
289
|
+
currentAttempt: attempt + 1
|
|
290
|
+
});
|
|
291
|
+
await new Promise((resolve) => {
|
|
292
|
+
const timeout = setTimeout(() => {
|
|
293
|
+
signal.removeEventListener("abort", onAbort);
|
|
294
|
+
resolve();
|
|
295
|
+
}, wait);
|
|
296
|
+
const onAbort = () => {
|
|
297
|
+
clearTimeout(timeout);
|
|
298
|
+
signal.removeEventListener("abort", onAbort);
|
|
299
|
+
resolve();
|
|
300
|
+
};
|
|
301
|
+
signal.addEventListener("abort", onAbort);
|
|
302
|
+
});
|
|
303
|
+
if (signal.aborted) return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} finally {
|
|
307
|
+
this.options.onSettled?.(args, this);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (totalTimeoutId) clearTimeout(totalTimeoutId);
|
|
311
|
+
this.#setState({ isExecuting: false });
|
|
312
|
+
this.options.onLastError?.(lastError, this);
|
|
313
|
+
this.options.onSettled?.(args, this);
|
|
314
|
+
if (this.options.throwOnError === "last" && isLastAttempt || this.options.throwOnError === true) throw lastError;
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Returns the current AbortSignal for the executing operation.
|
|
318
|
+
* Use this signal in your async function to make it cancellable.
|
|
319
|
+
* Returns null when not currently executing.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```typescript
|
|
323
|
+
* const retryer = new AsyncRetryer(async (userId: string) => {
|
|
324
|
+
* const signal = retryer.getAbortSignal()
|
|
325
|
+
* if (signal) {
|
|
326
|
+
* return fetch(`/api/users/${userId}`, { signal })
|
|
327
|
+
* }
|
|
328
|
+
* return fetch(`/api/users/${userId}`)
|
|
329
|
+
* })
|
|
330
|
+
*
|
|
331
|
+
* // Abort will now actually cancel the fetch
|
|
332
|
+
* retryer.abort()
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
getAbortSignal = () => {
|
|
336
|
+
return this.#abortController?.signal ?? null;
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* Cancels the current execution and any pending retries
|
|
340
|
+
* @param reason The reason for the abort (defaults to 'manual')
|
|
341
|
+
*/
|
|
342
|
+
abort = (reason = "manual") => {
|
|
343
|
+
if (this.#abortController) {
|
|
344
|
+
this.#abortController.abort();
|
|
345
|
+
this.#abortController = null;
|
|
346
|
+
this.#setState({ isExecuting: false });
|
|
347
|
+
this.options.onAbort?.(reason, this);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* Resets the retryer to its initial state
|
|
352
|
+
*/
|
|
353
|
+
reset = () => {
|
|
354
|
+
this.#setState(getDefaultAsyncRetryerState());
|
|
355
|
+
};
|
|
334
356
|
};
|
|
335
357
|
/**
|
|
336
358
|
* Creates a retry-enabled version of an async function. This is a convenience wrapper
|
|
@@ -367,5 +389,4 @@ function asyncRetry(fn, initialOptions = {}) {
|
|
|
367
389
|
}
|
|
368
390
|
|
|
369
391
|
//#endregion
|
|
370
|
-
export { AsyncRetryer, asyncRetry, asyncRetryerOptions };
|
|
371
|
-
//# sourceMappingURL=async-retryer.js.map
|
|
392
|
+
export { AsyncRetryer, asyncRetry, asyncRetryerOptions };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { AnyAsyncFunction } from "./types.js";
|
|
2
2
|
import { AsyncRetryer, AsyncRetryerOptions } from "./async-retryer.js";
|
|
3
3
|
import { Store } from "@tanstack/store";
|
|
4
|
-
|
|
5
4
|
//#region src/async-throttler.d.ts
|
|
6
|
-
interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
|
|
5
|
+
export interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
|
|
7
6
|
/**
|
|
8
7
|
* Number of function executions that have resulted in errors
|
|
9
8
|
*/
|
|
@@ -27,7 +26,7 @@ interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
|
|
|
27
26
|
/**
|
|
28
27
|
* The result from the most recent successful function execution
|
|
29
28
|
*/
|
|
30
|
-
lastResult: ReturnType<TFn
|
|
29
|
+
lastResult: Awaited<ReturnType<TFn>> | undefined;
|
|
31
30
|
/**
|
|
32
31
|
* Number of times maybeExecute has been called (for reduction calculations)
|
|
33
32
|
*/
|
|
@@ -52,7 +51,7 @@ interface AsyncThrottlerState<TFn extends AnyAsyncFunction> {
|
|
|
52
51
|
/**
|
|
53
52
|
* Options for configuring an async throttled function
|
|
54
53
|
*/
|
|
55
|
-
interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
54
|
+
export interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
56
55
|
/**
|
|
57
56
|
* Options for configuring the underlying async retryer
|
|
58
57
|
*/
|
|
@@ -90,7 +89,7 @@ interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
|
90
89
|
/**
|
|
91
90
|
* Optional function to call when the throttled function is executed
|
|
92
91
|
*/
|
|
93
|
-
onSuccess?: (result: ReturnType<TFn
|
|
92
|
+
onSuccess?: (result: Awaited<ReturnType<TFn>>, args: Parameters<TFn>, asyncThrottler: AsyncThrottler<TFn>) => void;
|
|
94
93
|
/**
|
|
95
94
|
* Whether to throw errors when they occur.
|
|
96
95
|
* Defaults to true if no onError handler is provided, false if an onError handler is provided.
|
|
@@ -112,7 +111,7 @@ interface AsyncThrottlerOptions<TFn extends AnyAsyncFunction> {
|
|
|
112
111
|
/**
|
|
113
112
|
* Utility function for sharing common `AsyncThrottlerOptions` options between different `AsyncThrottler` instances.
|
|
114
113
|
*/
|
|
115
|
-
declare function asyncThrottlerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncThrottlerOptions<TFn>> = Partial<AsyncThrottlerOptions<TFn>>>(options: TOptions): TOptions;
|
|
114
|
+
export declare function asyncThrottlerOptions<TFn extends AnyAsyncFunction = AnyAsyncFunction, TOptions extends Partial<AsyncThrottlerOptions<TFn>> = Partial<AsyncThrottlerOptions<TFn>>>(options: TOptions): TOptions;
|
|
116
115
|
/**
|
|
117
116
|
* A class that creates an async throttled function.
|
|
118
117
|
*
|
|
@@ -171,7 +170,7 @@ declare function asyncThrottlerOptions<TFn extends AnyAsyncFunction = AnyAsyncFu
|
|
|
171
170
|
* const result = await throttler.maybeExecute(inputElement.value);
|
|
172
171
|
* ```
|
|
173
172
|
*/
|
|
174
|
-
declare class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
173
|
+
export declare class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
175
174
|
#private;
|
|
176
175
|
fn: TFn;
|
|
177
176
|
readonly store: Store<Readonly<AsyncThrottlerState<TFn>>>;
|
|
@@ -205,11 +204,11 @@ declare class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
205
204
|
* await throttled.maybeExecute('c', 'd');
|
|
206
205
|
* ```
|
|
207
206
|
*/
|
|
208
|
-
maybeExecute: (...args: Parameters<TFn>) => Promise<ReturnType<TFn
|
|
207
|
+
maybeExecute: (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
|
|
209
208
|
/**
|
|
210
209
|
* Processes the current pending execution immediately
|
|
211
210
|
*/
|
|
212
|
-
flush: () => Promise<ReturnType<TFn
|
|
211
|
+
flush: () => Promise<Awaited<ReturnType<TFn>> | undefined>;
|
|
213
212
|
/**
|
|
214
213
|
* Returns the AbortSignal for a specific execution.
|
|
215
214
|
* If no maybeExecuteCount is provided, returns the signal for the most recent execution.
|
|
@@ -314,7 +313,5 @@ declare class AsyncThrottler<TFn extends AnyAsyncFunction> {
|
|
|
314
313
|
* const result = await throttled(inputElement.value);
|
|
315
314
|
* ```
|
|
316
315
|
*/
|
|
317
|
-
declare function asyncThrottle<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncThrottlerOptions<TFn>): (...args: Parameters<TFn>) => Promise<ReturnType<TFn
|
|
318
|
-
//#endregion
|
|
319
|
-
export { AsyncThrottler, AsyncThrottlerOptions, AsyncThrottlerState, asyncThrottle, asyncThrottlerOptions };
|
|
320
|
-
//# sourceMappingURL=async-throttler.d.ts.map
|
|
316
|
+
export declare function asyncThrottle<TFn extends AnyAsyncFunction>(fn: TFn, initialOptions: AsyncThrottlerOptions<TFn>): (...args: Parameters<TFn>) => Promise<Awaited<ReturnType<TFn>> | undefined>;
|
|
317
|
+
//#endregion
|