@ndriadev/futurable 2.3.3 → 3.0.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/CHANGELOG.md +95 -0
- package/README.md +453 -490
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +3027 -0
- package/dist/index.d.mts +3027 -0
- package/dist/index.d.ts +2922 -149
- package/dist/index.mjs +1 -0
- package/package.json +48 -46
- package/dist/example/index.d.ts +0 -2
- package/dist/example/index.d.ts.map +0 -1
- package/dist/futurable.cjs +0 -1
- package/dist/futurable.mjs +0 -437
- package/dist/index.d.ts.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/scripts/copy-resources.js +0 -37
- package/scripts/preInstall.js +0 -17
- package/scripts/server.js +0 -16
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,3027 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result type for safe operations that may succeed or fail.
|
|
3
|
+
* Provides a discriminated union for type-safe error handling without try-catch.
|
|
4
|
+
*
|
|
5
|
+
* @template T - The type of the success value
|
|
6
|
+
* @template E - The type of the error (defaults to Error)
|
|
7
|
+
*/
|
|
8
|
+
type SafeResult<T, E = Error> = {
|
|
9
|
+
success: true;
|
|
10
|
+
data: T;
|
|
11
|
+
error: null;
|
|
12
|
+
} | {
|
|
13
|
+
success: false;
|
|
14
|
+
data: null;
|
|
15
|
+
error: E;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A thenable-like interface that represents a value that may be available now, in the future, or never.
|
|
19
|
+
* Compatible with both Promises and Futurables, allowing for flexible composition.
|
|
20
|
+
*
|
|
21
|
+
* @template T - The type of the value that will be resolved
|
|
22
|
+
*/
|
|
23
|
+
interface FuturableLike<T> {
|
|
24
|
+
/**
|
|
25
|
+
* Attaches callbacks for the resolution and/or rejection of the Futurable.
|
|
26
|
+
*
|
|
27
|
+
* @template TResult1 - The type returned by the fulfillment callback
|
|
28
|
+
* @template TResult2 - The type returned by the rejection callback
|
|
29
|
+
* @param onfulfilled - The callback to execute when the Futurable is resolved
|
|
30
|
+
* @param onrejected - The callback to execute when the Futurable is rejected
|
|
31
|
+
* @returns A new Futurable for the completion of whichever callback is executed
|
|
32
|
+
*/
|
|
33
|
+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1> | FuturableLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2> | FuturableLike<TResult2>) | undefined | null): FuturableLike<TResult1 | TResult2>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Function signature for resolving a Futurable with a value.
|
|
37
|
+
* Accepts a direct value, a Promise, or another Futurable.
|
|
38
|
+
*
|
|
39
|
+
* @template T - The type of the value to resolve with
|
|
40
|
+
* @param value - The value, Promise, or Futurable to resolve with
|
|
41
|
+
*/
|
|
42
|
+
interface FuturableResolve<T> {
|
|
43
|
+
(value: T | FuturableLike<T> | PromiseLike<T>): void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Function signature for rejecting a Futurable with a reason.
|
|
47
|
+
*
|
|
48
|
+
* @param reason - The reason for rejection (typically an Error)
|
|
49
|
+
*/
|
|
50
|
+
interface FuturableReject {
|
|
51
|
+
(reason?: any): void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Utility methods and properties available within a Futurable executor.
|
|
55
|
+
* Provides advanced features like cancellation, delays, HTTP fetching, and signal management.
|
|
56
|
+
*
|
|
57
|
+
* @template T - The type of value the Futurable will resolve to
|
|
58
|
+
*/
|
|
59
|
+
interface FuturableUtils<T> {
|
|
60
|
+
/**
|
|
61
|
+
* Internal AbortSignal for cancellation support.
|
|
62
|
+
* This signal is aborted when the Futurable is cancelled.
|
|
63
|
+
*/
|
|
64
|
+
signal: AbortSignal;
|
|
65
|
+
/**
|
|
66
|
+
* Cancels the Futurable if it is pending or currently executing.
|
|
67
|
+
* Triggers the abort signal and executes any registered onCancel callbacks.
|
|
68
|
+
*/
|
|
69
|
+
cancel: () => void;
|
|
70
|
+
/**
|
|
71
|
+
* Registers a callback to be executed when the Futurable is cancelled.
|
|
72
|
+
* Multiple callbacks can be registered and will be executed in order.
|
|
73
|
+
*
|
|
74
|
+
* @param cb - The callback function to execute on cancellation
|
|
75
|
+
*/
|
|
76
|
+
onCancel: (cb: () => void) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Waits for a specified duration, then executes a callback and returns the result.
|
|
79
|
+
* The delay is cancellable via the Futurable's signal.
|
|
80
|
+
*
|
|
81
|
+
* @template TResult - The type returned by the callback
|
|
82
|
+
* @template TResult2 - The type in case of rejection (defaults to never)
|
|
83
|
+
* @param cb - The callback to execute after the timer expires
|
|
84
|
+
* @param timer - The delay duration in milliseconds
|
|
85
|
+
* @returns A new Futurable that resolves with the callback's result
|
|
86
|
+
*/
|
|
87
|
+
delay: <TResult = T, TResult2 = never>(cb: () => TResult, timer: number) => Futurable<TResult | TResult2>;
|
|
88
|
+
/**
|
|
89
|
+
* Pauses execution for a specified duration.
|
|
90
|
+
* Equivalent to delay with an empty callback.
|
|
91
|
+
*
|
|
92
|
+
* @param timer - The duration to wait in milliseconds
|
|
93
|
+
* @returns A Futurable that resolves after the timer expires
|
|
94
|
+
*/
|
|
95
|
+
sleep: (timer: number) => Futurable<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Extension of the native Fetch API with automatic cancellation support.
|
|
98
|
+
* The request is automatically cancelled if the Futurable is cancelled.
|
|
99
|
+
*
|
|
100
|
+
* @param url - The URL to fetch
|
|
101
|
+
* @param opts - Optional Fetch API options (signal will be automatically provided)
|
|
102
|
+
* @returns A Futurable that resolves with the Response object
|
|
103
|
+
*/
|
|
104
|
+
fetch: (url: string, opts?: RequestInit) => Futurable<Response>;
|
|
105
|
+
/**
|
|
106
|
+
* Converts a standard Promise into a Futurable with cancellation support.
|
|
107
|
+
* The original Promise cannot be cancelled, but the Futurable wrapper can be.
|
|
108
|
+
*
|
|
109
|
+
* @template TResult - The type the Promise resolves to
|
|
110
|
+
* @param promise - The Promise to convert
|
|
111
|
+
* @returns A Futurable wrapping the Promise
|
|
112
|
+
*/
|
|
113
|
+
futurizable: <TResult = any>(promise: Promise<TResult>) => Futurable<TResult>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Executor function signature for creating a new Futurable.
|
|
117
|
+
* Similar to the Promise executor, but with additional utilities for cancellation and async operations.
|
|
118
|
+
*
|
|
119
|
+
* @template T - The type of value the Futurable will resolve to
|
|
120
|
+
* @param resolve - Function to resolve the Futurable with a value
|
|
121
|
+
* @param reject - Function to reject the Futurable with a reason
|
|
122
|
+
* @param utils - Utility object containing cancellation and async helpers
|
|
123
|
+
*/
|
|
124
|
+
type FuturableExecutor<T> = (resolve: FuturableResolve<T>, reject: FuturableReject, utils: FuturableUtils<T>) => void;
|
|
125
|
+
/**
|
|
126
|
+
* An iterable collection of values that can be Futurables, Promises, or plain values.
|
|
127
|
+
* Used by static methods like Futurable.all(), Futurable.race(), etc.
|
|
128
|
+
*
|
|
129
|
+
* @template T - The type of values in the iterable
|
|
130
|
+
*/
|
|
131
|
+
type FuturableIterable<T = any> = Iterable<FuturableLike<T> | PromiseLike<T> | T>;
|
|
132
|
+
/**
|
|
133
|
+
* Return type of Futurable.withResolvers() static method.
|
|
134
|
+
* Provides direct access to the Futurable and its control functions.
|
|
135
|
+
*
|
|
136
|
+
* @template T - The type of value the Futurable will resolve to
|
|
137
|
+
*/
|
|
138
|
+
interface FuturableWithResolvers<T> {
|
|
139
|
+
/** The created Futurable or Promise instance */
|
|
140
|
+
promise: Futurable<T> | Promise<T>;
|
|
141
|
+
/** Function to resolve the Futurable with a value */
|
|
142
|
+
resolve: (value: T | PromiseLike<T> | FuturableLike<T>) => void;
|
|
143
|
+
/** Function to reject the Futurable with a reason */
|
|
144
|
+
reject: (reason?: any) => void;
|
|
145
|
+
/** Function to cancel the Futurable */
|
|
146
|
+
cancel: () => void;
|
|
147
|
+
/** Utility object with advanced Futurable features */
|
|
148
|
+
utils: FuturableUtils<T>;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Return type of Futurable.polling() static method.
|
|
152
|
+
* Provides controls for a polling operation.
|
|
153
|
+
*/
|
|
154
|
+
interface FuturablePollingController {
|
|
155
|
+
/** Stops the polling and cancels any pending operations */
|
|
156
|
+
cancel: () => void;
|
|
157
|
+
/** Registers an error handler for polling operations */
|
|
158
|
+
catch: (onrejected: (reason: unknown) => void) => void;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* A cancellable Promise implementation with extended async utilities.
|
|
162
|
+
*
|
|
163
|
+
* Futurable extends the native Promise API with:
|
|
164
|
+
* - Built-in cancellation via AbortSignal
|
|
165
|
+
* - Chainable delay and sleep operations
|
|
166
|
+
* - Integrated fetch with automatic cancellation
|
|
167
|
+
* - Polling capabilities
|
|
168
|
+
* - Promise-to-Futurable conversion
|
|
169
|
+
*
|
|
170
|
+
* @template T - The type of value this Futurable will resolve to
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* // Basic usage with cancellation
|
|
175
|
+
* const futurable = new Futurable((resolve, reject, { signal }) => {
|
|
176
|
+
* const timeoutId = setTimeout(() => resolve('done'), 5000);
|
|
177
|
+
* signal.addEventListener('abort', () => clearTimeout(timeoutId));
|
|
178
|
+
* });
|
|
179
|
+
*
|
|
180
|
+
* // Cancel after 1 second
|
|
181
|
+
* setTimeout(() => futurable.cancel(), 1000);
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare class Futurable<T> extends Promise<T> {
|
|
185
|
+
private controller;
|
|
186
|
+
private internalSignal;
|
|
187
|
+
private idsTimeout;
|
|
188
|
+
constructor(executor: FuturableExecutor<T>, signal?: AbortSignal);
|
|
189
|
+
static get [Symbol.species](): typeof Futurable;
|
|
190
|
+
get [Symbol.toStringTag](): string;
|
|
191
|
+
/**
|
|
192
|
+
* Returns the internal AbortSignal used for cancellation.
|
|
193
|
+
* This signal is aborted when cancel() is called.
|
|
194
|
+
*
|
|
195
|
+
* @returns The internal AbortSignal
|
|
196
|
+
*/
|
|
197
|
+
get signal(): AbortSignal;
|
|
198
|
+
private clearTimeout;
|
|
199
|
+
/**
|
|
200
|
+
* Attaches callbacks for the resolution and/or rejection of the Futurable.
|
|
201
|
+
* Chainable method that returns a new Futurable.
|
|
202
|
+
*
|
|
203
|
+
* @template TResult1 - Type returned by the fulfillment callback
|
|
204
|
+
* @template TResult2 - Type returned by the rejection callback
|
|
205
|
+
* @param onfulfilled - Callback executed when the Futurable is resolved
|
|
206
|
+
* @param onrejected - Callback executed when the Futurable is rejected
|
|
207
|
+
* @returns A new Futurable for the completion of the callback
|
|
208
|
+
*/
|
|
209
|
+
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1> | FuturableLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2> | FuturableLike<TResult2>) | undefined | null): Futurable<TResult1 | TResult2>;
|
|
210
|
+
/**
|
|
211
|
+
* Attaches a callback for only the rejection of the Futurable.
|
|
212
|
+
*
|
|
213
|
+
* @template TResult2 - Type returned by the rejection callback
|
|
214
|
+
* @param onRejected - Callback executed when the Futurable is rejected
|
|
215
|
+
* @returns A new Futurable
|
|
216
|
+
*/
|
|
217
|
+
catch<TResult2 = never>(onRejected: ((reason: any) => TResult2 | PromiseLike<TResult2> | FuturableLike<TResult2>) | undefined | null): Futurable<T | TResult2>;
|
|
218
|
+
/**
|
|
219
|
+
* Attaches a callback that is invoked when the Futurable is settled (fulfilled or rejected).
|
|
220
|
+
* The resolved value cannot be modified from the callback.
|
|
221
|
+
*
|
|
222
|
+
* @param onfinally - Callback executed when the Futurable settles
|
|
223
|
+
* @returns A new Futurable with the same value
|
|
224
|
+
*/
|
|
225
|
+
finally(onfinally: () => void | undefined | null): Futurable<T>;
|
|
226
|
+
/**
|
|
227
|
+
* Cancels the Futurable if it is pending or currently executing.
|
|
228
|
+
* Aborts the internal signal and triggers all registered onCancel callbacks.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```typescript
|
|
232
|
+
* const futurable = new Futurable((resolve) => {
|
|
233
|
+
* setTimeout(() => resolve('done'), 5000);
|
|
234
|
+
* });
|
|
235
|
+
* futurable.cancel(); // Cancels the operation
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
cancel(): void;
|
|
239
|
+
/**
|
|
240
|
+
* Waits for a specified duration, then executes a callback with the Futurable's value.
|
|
241
|
+
* The delay is cancellable via the Futurable's signal.
|
|
242
|
+
*
|
|
243
|
+
* @template TResult1 - Type returned by the callback
|
|
244
|
+
* @template TResult2 - Type in case of rejection
|
|
245
|
+
* @param cb - Callback executed after the delay, receives the resolved value
|
|
246
|
+
* @param timer - Delay duration in milliseconds
|
|
247
|
+
* @returns A new Futurable that resolves with the callback's result
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* Futurable.resolve(5)
|
|
252
|
+
* .delay(val => val * 2, 1000) // Wait 1s, then multiply by 2
|
|
253
|
+
* .then(result => console.log(result)); // Logs: 10
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
delay<TResult1 = T, TResult2 = never>(cb: (val: T) => TResult1 | PromiseLike<TResult1> | FuturableLike<TResult1>, timer: number): Futurable<TResult1 | TResult2>;
|
|
257
|
+
/**
|
|
258
|
+
* Pauses execution for a specified duration before passing through the value.
|
|
259
|
+
* Equivalent to delay with an identity callback.
|
|
260
|
+
*
|
|
261
|
+
* @param timer - Duration to wait in milliseconds
|
|
262
|
+
* @returns A new Futurable that resolves with the same value after the delay
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* Futurable.resolve('hello')
|
|
267
|
+
* .sleep(2000) // Wait 2 seconds
|
|
268
|
+
* .then(val => console.log(val)); // Logs: "hello" after 2s
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
sleep(timer: number): Futurable<T>;
|
|
272
|
+
/**
|
|
273
|
+
* Performs an HTTP fetch operation with automatic cancellation support.
|
|
274
|
+
* The request is automatically cancelled if the Futurable is cancelled.
|
|
275
|
+
*
|
|
276
|
+
* @param url - URL to fetch, or a function that receives the Futurable's value and returns a URL
|
|
277
|
+
* @param opts - Fetch options, or a function that receives the Futurable's value and returns fetch options
|
|
278
|
+
* @returns A new Futurable that resolves with the Response object
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* Futurable.resolve('users')
|
|
283
|
+
* .fetch(endpoint => `https://api.example.com/${endpoint}`)
|
|
284
|
+
* .then(response => response.json())
|
|
285
|
+
* .then(data => console.log(data));
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
fetch(url: string | ((val: T) => string), opts?: object | RequestInit | ((val: T) => RequestInit)): Futurable<Response>;
|
|
289
|
+
/**
|
|
290
|
+
* Registers a callback to be executed when the Futurable is cancelled.
|
|
291
|
+
* Useful for cleanup operations or aborting dependent async tasks.
|
|
292
|
+
*
|
|
293
|
+
* @template TResult1 - Type in case of resolution
|
|
294
|
+
* @template TResult2 - Type in case of rejection
|
|
295
|
+
* @param cb - Callback executed on cancellation
|
|
296
|
+
* @returns A new Futurable
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```typescript
|
|
300
|
+
* const futurable = new Futurable((resolve) => {
|
|
301
|
+
* const task = startLongTask();
|
|
302
|
+
* setTimeout(() => resolve('done'), 5000);
|
|
303
|
+
* }).onCancel(() => {
|
|
304
|
+
* console.log('Operation cancelled, cleaning up...');
|
|
305
|
+
* });
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
onCancel<TResult1 = void, TResult2 = never>(cb: () => void): Futurable<TResult1 | TResult2>;
|
|
309
|
+
/**
|
|
310
|
+
* Converts a Promise into a Futurable with cancellation support.
|
|
311
|
+
* The Promise can be provided directly or via a function that receives the current value.
|
|
312
|
+
*
|
|
313
|
+
* @template TResult1 - Type the Promise resolves to
|
|
314
|
+
* @template TResult2 - Type in case of rejection
|
|
315
|
+
* @param promise - Promise to convert, or a function that receives the Futurable's value and returns a Promise
|
|
316
|
+
* @returns A new Futurable wrapping the Promise
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* Futurable.resolve(123)
|
|
321
|
+
* .futurizable(val => fetch(`/api/${val}`).then(r => r.json()))
|
|
322
|
+
* .then(data => console.log(data));
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
futurizable<TResult1 = T, TResult2 = never>(promise: Promise<TResult1> | ((val?: T) => Promise<TResult1>)): Futurable<TResult1 | TResult2>;
|
|
326
|
+
/**
|
|
327
|
+
* Wraps the Futurable in a safe execution context that never throws.
|
|
328
|
+
* Returns a result object containing either the resolved value or the error,
|
|
329
|
+
* eliminating the need for try-catch blocks or .catch() handlers.
|
|
330
|
+
*
|
|
331
|
+
* This method is particularly useful in async/await contexts where you want
|
|
332
|
+
* to handle errors explicitly without wrapping code in try-catch blocks.
|
|
333
|
+
*
|
|
334
|
+
* @template TError - The type of the error (defaults to unknown)
|
|
335
|
+
* @returns A Futurable that resolves to a SafeResult containing either data or error
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* // Instead of try-catch:
|
|
340
|
+
* const result = await Futurable.fetch('/api/data')
|
|
341
|
+
* .then(r => r.json())
|
|
342
|
+
* .safe();
|
|
343
|
+
*
|
|
344
|
+
* if (result.success) {
|
|
345
|
+
* console.log('Data:', result.data);
|
|
346
|
+
* } else {
|
|
347
|
+
* console.error('Error:', result.error);
|
|
348
|
+
* }
|
|
349
|
+
* ```
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* ```typescript
|
|
353
|
+
* // Chaining multiple operations safely:
|
|
354
|
+
* const result = await Futurable.resolve(5)
|
|
355
|
+
* .delay(val => val * 2, 1000)
|
|
356
|
+
* .fetch(val => `/api/item/${val}`)
|
|
357
|
+
* .futurizable(r => r.json())
|
|
358
|
+
* .safe();
|
|
359
|
+
*
|
|
360
|
+
* if (result.success) {
|
|
361
|
+
* // TypeScript knows result.data is the JSON response
|
|
362
|
+
* processData(result.data);
|
|
363
|
+
* } else {
|
|
364
|
+
* // TypeScript knows result.error exists
|
|
365
|
+
* logError(result.error);
|
|
366
|
+
* }
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
safe<TError = unknown>(): Futurable<SafeResult<T, TError>>;
|
|
370
|
+
/**
|
|
371
|
+
* Creates a new resolved Futurable without a value (resolves to void).
|
|
372
|
+
*
|
|
373
|
+
* @returns A Futurable that immediately resolves to void
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* Futurable.resolve()
|
|
378
|
+
* .delay(() => 'hello', 1000)
|
|
379
|
+
* .then(val => console.log(val)); // Logs: "hello" after 1s
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
static resolve(): Futurable<void>;
|
|
383
|
+
/**
|
|
384
|
+
* Creates a new resolved Futurable for the provided value.
|
|
385
|
+
*
|
|
386
|
+
* @template T - Type of the value to resolve with
|
|
387
|
+
* @param value - The value, Promise, or Futurable to resolve with
|
|
388
|
+
* @param signal - Optional AbortSignal for cancellation coordination
|
|
389
|
+
* @returns A Futurable that immediately resolves with the value
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* Futurable.resolve(42)
|
|
394
|
+
* .then(val => console.log(val)); // Logs: 42
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
static resolve<T = any>(value: T | PromiseLike<T> | FuturableLike<T>, signal?: AbortSignal): Futurable<T>;
|
|
398
|
+
/**
|
|
399
|
+
* Creates a new rejected Futurable for the provided reason.
|
|
400
|
+
*
|
|
401
|
+
* @template T - Type of value (never used for rejected Futurables)
|
|
402
|
+
* @param reason - The reason for rejection (typically an Error)
|
|
403
|
+
* @param signal - Optional AbortSignal for cancellation coordination
|
|
404
|
+
* @returns A Futurable that immediately rejects with the reason
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```typescript
|
|
408
|
+
* Futurable.reject(new Error('Failed'))
|
|
409
|
+
* .catch(err => console.error(err)); // Logs the error
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
static reject<T = never>(reason?: any, signal?: AbortSignal): Futurable<T>;
|
|
413
|
+
/**
|
|
414
|
+
* Creates a Futurable that resolves when cancelled.
|
|
415
|
+
* Useful for creating cancellation-aware cleanup logic.
|
|
416
|
+
*
|
|
417
|
+
* @template T - Type returned by the callback
|
|
418
|
+
* @param options - Configuration object
|
|
419
|
+
* @param options.cb - Callback executed on cancellation
|
|
420
|
+
* @param options.signal - Optional external AbortSignal
|
|
421
|
+
* @returns A Futurable that resolves with the callback's result when cancelled
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```typescript
|
|
425
|
+
* const cleanup = Futurable.onCancel({
|
|
426
|
+
* cb: () => console.log('Cleanup performed')
|
|
427
|
+
* });
|
|
428
|
+
* cleanup.cancel(); // Triggers the callback
|
|
429
|
+
* ```
|
|
430
|
+
*/
|
|
431
|
+
static onCancel<T = void>({ cb, signal }: {
|
|
432
|
+
cb: () => T;
|
|
433
|
+
signal?: AbortSignal;
|
|
434
|
+
}): Futurable<T>;
|
|
435
|
+
/**
|
|
436
|
+
* Creates a Futurable that executes a callback after a specified delay.
|
|
437
|
+
*
|
|
438
|
+
* @template T - Type returned by the callback
|
|
439
|
+
* @template TResult2 - Type in case of rejection
|
|
440
|
+
* @param options - Configuration object
|
|
441
|
+
* @param options.cb - Callback to execute after the delay
|
|
442
|
+
* @param options.timer - Delay duration in milliseconds
|
|
443
|
+
* @param options.signal - Optional AbortSignal for cancellation
|
|
444
|
+
* @returns A Futurable that resolves with the callback's result after the delay
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```typescript
|
|
448
|
+
* Futurable.delay({
|
|
449
|
+
* cb: () => 'Hello after delay',
|
|
450
|
+
* timer: 2000
|
|
451
|
+
* }).then(msg => console.log(msg)); // Logs after 2s
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
static delay<T = any, TResult2 = never>({ cb, timer, signal }: {
|
|
455
|
+
cb: () => T;
|
|
456
|
+
timer: number;
|
|
457
|
+
signal?: AbortSignal;
|
|
458
|
+
}): Futurable<T | TResult2>;
|
|
459
|
+
/**
|
|
460
|
+
* Creates a Futurable that resolves after a specified delay.
|
|
461
|
+
* Equivalent to delay with an empty callback.
|
|
462
|
+
*
|
|
463
|
+
* @param options - Configuration object
|
|
464
|
+
* @param options.timer - Duration to wait in milliseconds
|
|
465
|
+
* @param options.signal - Optional AbortSignal for cancellation
|
|
466
|
+
* @returns A Futurable that resolves after the delay
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* ```typescript
|
|
470
|
+
* Futurable.sleep({ timer: 3000 })
|
|
471
|
+
* .then(() => console.log('3 seconds passed'));
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
static sleep({ timer, signal }: {
|
|
475
|
+
timer: number;
|
|
476
|
+
signal?: AbortSignal;
|
|
477
|
+
}): Futurable<void>;
|
|
478
|
+
/**
|
|
479
|
+
* Performs an HTTP fetch operation with cancellation support.
|
|
480
|
+
*
|
|
481
|
+
* @param url - The URL to fetch
|
|
482
|
+
* @param opts - Optional Fetch API options (if signal is provided, it overrides the internal one)
|
|
483
|
+
* @returns A Futurable that resolves with the Response object
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* ```typescript
|
|
487
|
+
* Futurable.fetch('https://api.example.com/data')
|
|
488
|
+
* .then(response => response.json())
|
|
489
|
+
* .then(data => console.log(data));
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
static fetch(url: string, opts?: RequestInit): Futurable<Response>;
|
|
493
|
+
/**
|
|
494
|
+
* Converts a Promise into a Futurable with cancellation support.
|
|
495
|
+
* Note: The original Promise cannot be cancelled, but the Futurable wrapper can be.
|
|
496
|
+
*
|
|
497
|
+
* @template TResult1 - Type the Promise resolves to
|
|
498
|
+
* @template TResult2 - Type in case of rejection
|
|
499
|
+
* @param options - Configuration object
|
|
500
|
+
* @param options.promise - The Promise to convert
|
|
501
|
+
* @param options.signal - Optional AbortSignal for cancellation
|
|
502
|
+
* @returns A Futurable wrapping the Promise
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* ```typescript
|
|
506
|
+
* const promise = fetch('/api/data').then(r => r.json());
|
|
507
|
+
* Futurable.futurizable({ promise })
|
|
508
|
+
* .then(data => console.log(data));
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
511
|
+
static futurizable<TResult1 = any, TResult2 = never>({ promise, signal }: {
|
|
512
|
+
promise: Promise<TResult1>;
|
|
513
|
+
signal?: AbortSignal;
|
|
514
|
+
}): Futurable<TResult1 | TResult2>;
|
|
515
|
+
private static handleValues;
|
|
516
|
+
/**
|
|
517
|
+
* Creates a Futurable that resolves when all provided Futurables/Promises resolve,
|
|
518
|
+
* or rejects when any of them rejects. Supports cancellation of all pending operations.
|
|
519
|
+
*
|
|
520
|
+
* @template T - Tuple type of input values
|
|
521
|
+
* @param values - Array of Futurables, Promises, or plain values
|
|
522
|
+
* @param signal - Optional AbortSignal that cancels all operations when aborted
|
|
523
|
+
* @returns A Futurable that resolves with an array of all resolved values
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```typescript
|
|
527
|
+
* const all = Futurable.all([
|
|
528
|
+
* Futurable.delay({ cb: () => 1, timer: 100 }),
|
|
529
|
+
* Futurable.delay({ cb: () => 2, timer: 200 }),
|
|
530
|
+
* Promise.resolve(3)
|
|
531
|
+
* ]);
|
|
532
|
+
* all.then(results => console.log(results)); // [1, 2, 3]
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
535
|
+
static all<T extends readonly unknown[] | []>(values: T, signal?: AbortSignal): Futurable<{
|
|
536
|
+
-readonly [P in keyof T]: Awaited<T[P]>;
|
|
537
|
+
}>;
|
|
538
|
+
/**
|
|
539
|
+
* Creates a Futurable that resolves when all provided Futurables/Promises settle
|
|
540
|
+
* (either resolve or reject). Returns an array of result objects indicating the outcome.
|
|
541
|
+
*
|
|
542
|
+
* @template T - Tuple type of input values
|
|
543
|
+
* @param values - Array of Futurables, Promises, or plain values
|
|
544
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
545
|
+
* @returns A Futurable that resolves with an array of PromiseSettledResult objects
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* ```typescript
|
|
549
|
+
* Futurable.allSettled([
|
|
550
|
+
* Futurable.resolve(1),
|
|
551
|
+
* Futurable.reject('error'),
|
|
552
|
+
* Promise.resolve(3)
|
|
553
|
+
* ]).then(results => {
|
|
554
|
+
* // results[0]: { status: 'fulfilled', value: 1 }
|
|
555
|
+
* // results[1]: { status: 'rejected', reason: 'error' }
|
|
556
|
+
* // results[2]: { status: 'fulfilled', value: 3 }
|
|
557
|
+
* });
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
static allSettled<T extends readonly unknown[] | []>(values: T, signal?: AbortSignal): Futurable<{
|
|
561
|
+
-readonly [P in keyof T]: PromiseSettledResult<Awaited<T[P]>>;
|
|
562
|
+
}>;
|
|
563
|
+
/**
|
|
564
|
+
* Creates a Futurable that resolves or rejects as soon as any of the provided
|
|
565
|
+
* Futurables/Promises resolves or rejects. Cancels all other pending operations.
|
|
566
|
+
*
|
|
567
|
+
* @template T - Tuple type of input values
|
|
568
|
+
* @param values - Array of Futurables, Promises, or plain values
|
|
569
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
570
|
+
* @returns A Futurable that settles with the first settled value/reason
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* ```typescript
|
|
574
|
+
* Futurable.race([
|
|
575
|
+
* Futurable.delay({ cb: () => 'slow', timer: 2000 }),
|
|
576
|
+
* Futurable.delay({ cb: () => 'fast', timer: 100 })
|
|
577
|
+
* ]).then(result => console.log(result)); // 'fast'
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
static race<T extends readonly unknown[] | []>(values: T, signal?: AbortSignal): Futurable<Awaited<T[number]>>;
|
|
581
|
+
/**
|
|
582
|
+
* Creates a Futurable that resolves as soon as any of the provided Futurables/Promises
|
|
583
|
+
* successfully resolves. Rejects with an AggregateError if all of them reject.
|
|
584
|
+
*
|
|
585
|
+
* @template T - Tuple type of input values
|
|
586
|
+
* @param values - Array of Futurables, Promises, or plain values
|
|
587
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
588
|
+
* @returns A Futurable that resolves with the first successful value
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```typescript
|
|
592
|
+
* Futurable.any([
|
|
593
|
+
* Futurable.reject('error1'),
|
|
594
|
+
* Futurable.delay({ cb: () => 'success', timer: 100 }),
|
|
595
|
+
* Futurable.reject('error2')
|
|
596
|
+
* ]).then(result => console.log(result)); // 'success'
|
|
597
|
+
* ```
|
|
598
|
+
*/
|
|
599
|
+
static any<T extends readonly unknown[] | []>(values: T, signal?: AbortSignal): Futurable<Awaited<T[number]>>;
|
|
600
|
+
/**
|
|
601
|
+
* Creates a polling service that repeatedly executes a function at regular intervals.
|
|
602
|
+
* Supports cancellation and error handling.
|
|
603
|
+
*
|
|
604
|
+
* @template T - Type returned by the polling function
|
|
605
|
+
* @param fun - Function to execute on each poll (can return a Futurable, Promise, or plain value)
|
|
606
|
+
* @param options - Configuration object
|
|
607
|
+
* @param options.interval - Interval between polls in milliseconds
|
|
608
|
+
* @param options.signal - Optional AbortSignal to stop polling
|
|
609
|
+
* @param options.immediate - If true, executes the function immediately before starting the interval
|
|
610
|
+
* @returns A controller object with cancel() and catch() methods
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```typescript
|
|
614
|
+
* const polling = Futurable.polling(
|
|
615
|
+
* () => fetch('/api/status').then(r => r.json()),
|
|
616
|
+
* { interval: 5000, immediate: true }
|
|
617
|
+
* );
|
|
618
|
+
*
|
|
619
|
+
* polling.catch(err => console.error('Polling error:', err));
|
|
620
|
+
*
|
|
621
|
+
* // Stop polling after 30 seconds
|
|
622
|
+
* setTimeout(() => polling.cancel(), 30000);
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
static polling<T>(fun: () => Futurable<T> | Promise<T> | T, { interval, signal, immediate }: {
|
|
626
|
+
interval: number;
|
|
627
|
+
signal?: AbortSignal;
|
|
628
|
+
immediate?: boolean;
|
|
629
|
+
}): FuturablePollingController;
|
|
630
|
+
/**
|
|
631
|
+
* Creates a new Futurable and returns it along with its control functions.
|
|
632
|
+
* Extension of the Promise.withResolvers() static method with cancellation support.
|
|
633
|
+
*
|
|
634
|
+
* @template T - Type of value the Futurable will resolve to
|
|
635
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
636
|
+
* @returns An object containing the Futurable and its control functions
|
|
637
|
+
*
|
|
638
|
+
* @example
|
|
639
|
+
* ```typescript
|
|
640
|
+
* const { promise, resolve, reject, cancel } = Futurable.withResolvers<number>();
|
|
641
|
+
*
|
|
642
|
+
* // Resolve from elsewhere
|
|
643
|
+
* setTimeout(() => resolve(42), 1000);
|
|
644
|
+
*
|
|
645
|
+
* // Or cancel
|
|
646
|
+
* cancel();
|
|
647
|
+
*
|
|
648
|
+
* promise.then(val => console.log(val));
|
|
649
|
+
* ```
|
|
650
|
+
*/
|
|
651
|
+
static withResolvers<T>(signal?: AbortSignal): FuturableWithResolvers<T>;
|
|
652
|
+
/**
|
|
653
|
+
* Creates a Futurable that wraps an executor in a safe execution context.
|
|
654
|
+
* The resulting Futurable never rejects - instead, it resolves with a result
|
|
655
|
+
* object containing either the success value or the error.
|
|
656
|
+
*
|
|
657
|
+
* This is useful when you want to create a Futurable that handles its own errors
|
|
658
|
+
* internally and always resolves with a discriminated result type.
|
|
659
|
+
*
|
|
660
|
+
* @template T - The type of the success value
|
|
661
|
+
* @template E - The type of the error (defaults to unknown)
|
|
662
|
+
* @param executor - The Futurable executor function
|
|
663
|
+
* @param signal - Optional AbortSignal for cancellation coordination
|
|
664
|
+
* @returns A Futurable that always resolves to a SafeResult
|
|
665
|
+
*
|
|
666
|
+
* @example
|
|
667
|
+
* ```typescript
|
|
668
|
+
* const result = await Futurable.safe<number>(
|
|
669
|
+
* (resolve, reject, { fetch }) => {
|
|
670
|
+
* fetch('/api/data')
|
|
671
|
+
* .then(r => r.json())
|
|
672
|
+
* .then(data => resolve(data.value))
|
|
673
|
+
* .catch(reject);
|
|
674
|
+
* }
|
|
675
|
+
* );
|
|
676
|
+
*
|
|
677
|
+
* if (result.success) {
|
|
678
|
+
* console.log('Value:', result.data);
|
|
679
|
+
* } else {
|
|
680
|
+
* console.error('Failed:', result.error);
|
|
681
|
+
* }
|
|
682
|
+
* ```
|
|
683
|
+
*
|
|
684
|
+
* @example
|
|
685
|
+
* ```typescript
|
|
686
|
+
* // With custom error type:
|
|
687
|
+
* interface ApiError {
|
|
688
|
+
* code: string;
|
|
689
|
+
* message: string;
|
|
690
|
+
* }
|
|
691
|
+
*
|
|
692
|
+
* const result = await Futurable.safe<User, ApiError>(
|
|
693
|
+
* (resolve, reject, { fetch }) => {
|
|
694
|
+
* fetch('/api/user')
|
|
695
|
+
* .then(r => r.ok ? r.json() : reject({ code: 'HTTP_ERROR', message: r.statusText }))
|
|
696
|
+
* .then(resolve)
|
|
697
|
+
* .catch(err => reject({ code: 'NETWORK_ERROR', message: err.message }));
|
|
698
|
+
* }
|
|
699
|
+
* );
|
|
700
|
+
*
|
|
701
|
+
* if (!result.success) {
|
|
702
|
+
* switch (result.error.code) {
|
|
703
|
+
* case 'HTTP_ERROR':
|
|
704
|
+
* // Handle HTTP errors
|
|
705
|
+
* break;
|
|
706
|
+
* case 'NETWORK_ERROR':
|
|
707
|
+
* // Handle network errors
|
|
708
|
+
* break;
|
|
709
|
+
* }
|
|
710
|
+
* }
|
|
711
|
+
* ```
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* // Combining with cancellation:
|
|
716
|
+
* const controller = new AbortController();
|
|
717
|
+
*
|
|
718
|
+
* const result = await Futurable.safe<string>(
|
|
719
|
+
* (resolve, reject, { signal, fetch }) => {
|
|
720
|
+
* fetch('/api/slow-endpoint')
|
|
721
|
+
* .then(r => r.text())
|
|
722
|
+
* .then(resolve)
|
|
723
|
+
* .catch(reject);
|
|
724
|
+
* },
|
|
725
|
+
* controller.signal
|
|
726
|
+
* );
|
|
727
|
+
*
|
|
728
|
+
* // Cancel after 5 seconds
|
|
729
|
+
* setTimeout(() => controller.abort(), 5000);
|
|
730
|
+
* ```
|
|
731
|
+
*/
|
|
732
|
+
static safe<T, E = unknown>(executor: FuturableExecutor<T>, signal?: AbortSignal): Futurable<SafeResult<T, E>>;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Configuration options for memoization behavior.
|
|
737
|
+
*
|
|
738
|
+
* @template T - The type of value being memoized
|
|
739
|
+
*
|
|
740
|
+
* @property enabled - Whether memoization is active
|
|
741
|
+
* @property catchErrors - If true, caches the result even when the execution rejects
|
|
742
|
+
* @property instance - The cached Futurable instance (if memoization is active)
|
|
743
|
+
*/
|
|
744
|
+
type MemoizeOptions<T> = {
|
|
745
|
+
enabled: boolean;
|
|
746
|
+
catchErrors?: boolean;
|
|
747
|
+
instance?: Futurable<T>;
|
|
748
|
+
};
|
|
749
|
+
/**
|
|
750
|
+
* Event hooks for monitoring task limiter lifecycle.
|
|
751
|
+
* All hooks are optional and provide insight into task execution flow.
|
|
752
|
+
*
|
|
753
|
+
* @property onActive - Called when a task starts executing
|
|
754
|
+
* @property onCompleted - Called when a task completes successfully with its result
|
|
755
|
+
* @property onError - Called when a task fails with an error
|
|
756
|
+
* @property onIdle - Called when all tasks have completed and the queue is empty
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```typescript
|
|
760
|
+
* const events: LimiterEvents = {
|
|
761
|
+
* onActive: (task) => console.log('Task started:', task),
|
|
762
|
+
* onCompleted: (result) => console.log('Task completed:', result),
|
|
763
|
+
* onError: (error) => console.error('Task failed:', error),
|
|
764
|
+
* onIdle: () => console.log('All tasks finished')
|
|
765
|
+
* };
|
|
766
|
+
* ```
|
|
767
|
+
*/
|
|
768
|
+
interface LimiterEvents {
|
|
769
|
+
onActive?: (task: any) => void;
|
|
770
|
+
onCompleted?: (result: any) => void;
|
|
771
|
+
onError?: (error: any) => void;
|
|
772
|
+
onIdle?: () => void;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* A higher-order function that wraps tasks with concurrency limiting.
|
|
776
|
+
*
|
|
777
|
+
* Acts as both a function and an object with readonly properties.
|
|
778
|
+
* The function takes a task and returns a new task that respects the concurrency limit.
|
|
779
|
+
*
|
|
780
|
+
* @property activeCount - Number of tasks currently executing
|
|
781
|
+
* @property pendingCount - Number of tasks waiting in the queue
|
|
782
|
+
* @property concurrency - Maximum number of concurrent tasks allowed
|
|
783
|
+
*
|
|
784
|
+
* @example
|
|
785
|
+
* ```typescript
|
|
786
|
+
* const limiter = FuturableTask.createLimiter(2);
|
|
787
|
+
*
|
|
788
|
+
* console.log(limiter.concurrency); // 2
|
|
789
|
+
* console.log(limiter.activeCount); // 0
|
|
790
|
+
* console.log(limiter.pendingCount); // 0
|
|
791
|
+
*
|
|
792
|
+
* const limitedTask = limiter(myTask);
|
|
793
|
+
* ```
|
|
794
|
+
*/
|
|
795
|
+
type FuturableTaskLimiter = (<T>(task: FuturableTask<T>) => FuturableTask<T>) & {
|
|
796
|
+
readonly activeCount: number;
|
|
797
|
+
readonly pendingCount: number;
|
|
798
|
+
readonly concurrency: number;
|
|
799
|
+
};
|
|
800
|
+
/**
|
|
801
|
+
* Lazy computation wrapper for deferred execution.
|
|
802
|
+
*
|
|
803
|
+
* Unlike Futurable (which extends Promise and is eager), FuturableTask is lazy:
|
|
804
|
+
* - Creation doesn't trigger execution
|
|
805
|
+
* - Can be composed without side effects
|
|
806
|
+
* - Execution happens only when run() is called
|
|
807
|
+
* - Can be run multiple times (each run is independent)
|
|
808
|
+
* - Provides functional composition methods
|
|
809
|
+
* - Supports cancellation at both task and execution level
|
|
810
|
+
*
|
|
811
|
+
* @template T - The type of value this task will eventually produce
|
|
812
|
+
*
|
|
813
|
+
* @example
|
|
814
|
+
* ```typescript
|
|
815
|
+
* // Creating a task doesn't execute it
|
|
816
|
+
* const task = FuturableTask.of(() => {
|
|
817
|
+
* console.log('Executing!');
|
|
818
|
+
* return fetch('/api/data');
|
|
819
|
+
* });
|
|
820
|
+
* // Nothing logged yet
|
|
821
|
+
*
|
|
822
|
+
* const result1 = await task.run(); // Logs: "Executing!" and fetches
|
|
823
|
+
* const result2 = await task.run(); // Logs: "Executing!" again (independent execution)
|
|
824
|
+
* ```
|
|
825
|
+
*
|
|
826
|
+
* @example
|
|
827
|
+
* ```typescript
|
|
828
|
+
* // Functional composition without execution
|
|
829
|
+
* const pipeline = FuturableTask
|
|
830
|
+
* .of(() => fetch('/users'))
|
|
831
|
+
* .map(res => res.json())
|
|
832
|
+
* .map(users => users.filter(u => u.active))
|
|
833
|
+
* .retry(3)
|
|
834
|
+
* .timeout(5000);
|
|
835
|
+
*
|
|
836
|
+
* // Only now does execution happen
|
|
837
|
+
* const activeUsers = await pipeline.run();
|
|
838
|
+
* ```
|
|
839
|
+
*
|
|
840
|
+
* @example
|
|
841
|
+
* ```typescript
|
|
842
|
+
* // Cancellation support
|
|
843
|
+
* const task = FuturableTask.of(() => longOperation())
|
|
844
|
+
* .onCancel(() => console.log('Cleanup'));
|
|
845
|
+
*
|
|
846
|
+
* const run = task.run();
|
|
847
|
+
* task.cancel(); // Logs: "Cleanup", cancels the operation
|
|
848
|
+
* ```
|
|
849
|
+
*/
|
|
850
|
+
declare class FuturableTask<T> {
|
|
851
|
+
private readonly executor;
|
|
852
|
+
/**
|
|
853
|
+
* Internal AbortController that manages task cancellation.
|
|
854
|
+
* Created in the constructor and used to abort all executions of this task.
|
|
855
|
+
*
|
|
856
|
+
* @private
|
|
857
|
+
* @readonly
|
|
858
|
+
*/
|
|
859
|
+
private readonly controller;
|
|
860
|
+
/**
|
|
861
|
+
* Array of callbacks to execute when the task is cancelled.
|
|
862
|
+
* These callbacks are executed eagerly (even if the task was never run).
|
|
863
|
+
*
|
|
864
|
+
* @private
|
|
865
|
+
* @readonly
|
|
866
|
+
*/
|
|
867
|
+
private readonly cancelCallbacks;
|
|
868
|
+
/**
|
|
869
|
+
* Configuration object for memoization behavior.
|
|
870
|
+
* When enabled, caches the first execution result and reuses it.
|
|
871
|
+
*
|
|
872
|
+
* @private
|
|
873
|
+
* @readonly
|
|
874
|
+
*/
|
|
875
|
+
private readonly memoizeOptions;
|
|
876
|
+
/**
|
|
877
|
+
* Reference to the source task (if this task is debounced).
|
|
878
|
+
* When calling debounce() on an already debounced task, this points to the original source.
|
|
879
|
+
*
|
|
880
|
+
* @private
|
|
881
|
+
*/
|
|
882
|
+
private sourceTask?;
|
|
883
|
+
/**
|
|
884
|
+
* Creates a new FuturableTask.
|
|
885
|
+
*
|
|
886
|
+
* The executor function is NOT invoked until run() is called.
|
|
887
|
+
* If an external signal is provided, aborting it will also cancel this task.
|
|
888
|
+
*
|
|
889
|
+
* @param executor - The executor function that defines the computation.
|
|
890
|
+
* Receives resolve, reject, and utils (with signal, onCancel, delay, sleep, fetch, etc.)
|
|
891
|
+
* @param externalSignal - Optional AbortSignal that will cancel this task when aborted.
|
|
892
|
+
* Useful for coordinating cancellation with parent operations.
|
|
893
|
+
*
|
|
894
|
+
* @example
|
|
895
|
+
* ```typescript
|
|
896
|
+
* // Basic task
|
|
897
|
+
* const task = new FuturableTask((resolve, reject, utils) => {
|
|
898
|
+
* setTimeout(() => resolve('done'), 1000);
|
|
899
|
+
* });
|
|
900
|
+
* ```
|
|
901
|
+
*
|
|
902
|
+
* @example
|
|
903
|
+
* ```typescript
|
|
904
|
+
* // With cancellation support
|
|
905
|
+
* const task = new FuturableTask((resolve, reject, utils) => {
|
|
906
|
+
* const timeoutId = setTimeout(() => resolve('done'), 5000);
|
|
907
|
+
*
|
|
908
|
+
* utils.onCancel(() => {
|
|
909
|
+
* console.log('Cancelled!');
|
|
910
|
+
* clearTimeout(timeoutId);
|
|
911
|
+
* });
|
|
912
|
+
* });
|
|
913
|
+
* ```
|
|
914
|
+
*
|
|
915
|
+
* @example
|
|
916
|
+
* ```typescript
|
|
917
|
+
* // With external signal
|
|
918
|
+
* const controller = new AbortController();
|
|
919
|
+
* const task = new FuturableTask((res) => {
|
|
920
|
+
* res('value');
|
|
921
|
+
* }, controller.signal);
|
|
922
|
+
*
|
|
923
|
+
* controller.abort(); // Cancels the task
|
|
924
|
+
* ```
|
|
925
|
+
*/
|
|
926
|
+
constructor(executor: FuturableExecutor<T>, externalSignal?: AbortSignal);
|
|
927
|
+
/**
|
|
928
|
+
* Returns the internal AbortSignal for this task.
|
|
929
|
+
*
|
|
930
|
+
* This signal is aborted when cancel() is called on the task.
|
|
931
|
+
* All executions created by run() will listen to this signal.
|
|
932
|
+
*
|
|
933
|
+
* @returns The internal AbortSignal
|
|
934
|
+
*
|
|
935
|
+
* @example
|
|
936
|
+
* ```typescript
|
|
937
|
+
* const task = FuturableTask.of(() => fetch('/api/data'));
|
|
938
|
+
*
|
|
939
|
+
* console.log(task.signal.aborted); // false
|
|
940
|
+
* task.cancel();
|
|
941
|
+
* console.log(task.signal.aborted); // true
|
|
942
|
+
* ```
|
|
943
|
+
*/
|
|
944
|
+
get signal(): AbortSignal;
|
|
945
|
+
/**
|
|
946
|
+
* Cancels all running and future executions of this task.
|
|
947
|
+
*
|
|
948
|
+
* This will:
|
|
949
|
+
* 1. Abort the internal signal
|
|
950
|
+
* 2. Execute all registered task-level onCancel callbacks
|
|
951
|
+
* 3. Cancel all Futurables created by run() that haven't completed yet
|
|
952
|
+
* 4. Prevent new executions from starting (they will be pending)
|
|
953
|
+
*
|
|
954
|
+
* Note: This is idempotent - calling it multiple times has no additional effect.
|
|
955
|
+
*
|
|
956
|
+
* @example
|
|
957
|
+
* ```typescript
|
|
958
|
+
* const task = FuturableTask.of(() => longRunningOperation());
|
|
959
|
+
* const run1 = task.run();
|
|
960
|
+
* const run2 = task.run();
|
|
961
|
+
*
|
|
962
|
+
* task.cancel(); // Cancels both run1 and run2
|
|
963
|
+
*
|
|
964
|
+
* const run3 = task.run(); // This will be pending (never resolves)
|
|
965
|
+
* ```
|
|
966
|
+
*
|
|
967
|
+
* @example
|
|
968
|
+
* ```typescript
|
|
969
|
+
* const task = FuturableTask.of(() => fetch('/api'))
|
|
970
|
+
* .onCancel(() => console.log('Task cancelled'));
|
|
971
|
+
*
|
|
972
|
+
* task.cancel(); // Logs: "Task cancelled"
|
|
973
|
+
* task.cancel(); // Does nothing (already cancelled)
|
|
974
|
+
* ```
|
|
975
|
+
*/
|
|
976
|
+
cancel(): void;
|
|
977
|
+
/**
|
|
978
|
+
* Registers a callback to be executed when the task is cancelled.
|
|
979
|
+
*
|
|
980
|
+
* IMPORTANT: This is an eager callback - it executes when task.cancel() is called,
|
|
981
|
+
* even if the task has never been run. This is different from registering callbacks
|
|
982
|
+
* inside the executor with utils.onCancel, which are only called if the task was
|
|
983
|
+
* actively running when cancelled.
|
|
984
|
+
*
|
|
985
|
+
* Multiple callbacks can be registered and will execute in order.
|
|
986
|
+
*
|
|
987
|
+
* @param cb - Callback to execute on cancellation
|
|
988
|
+
* @returns The same FuturableTask instance for chaining
|
|
989
|
+
*
|
|
990
|
+
* @example
|
|
991
|
+
* ```typescript
|
|
992
|
+
* const task = FuturableTask.of(() => fetch('/api/data'))
|
|
993
|
+
* .onCancel(() => console.log('Task cancelled'))
|
|
994
|
+
* .onCancel(() => console.log('Cleanup complete'));
|
|
995
|
+
*
|
|
996
|
+
* task.cancel();
|
|
997
|
+
* // Logs: "Task cancelled"
|
|
998
|
+
* // Logs: "Cleanup complete"
|
|
999
|
+
* // (even without calling run())
|
|
1000
|
+
* ```
|
|
1001
|
+
*
|
|
1002
|
+
* @example
|
|
1003
|
+
* ```typescript
|
|
1004
|
+
* // Cleanup external resources
|
|
1005
|
+
* const ws = new WebSocket('ws://...');
|
|
1006
|
+
*
|
|
1007
|
+
* const task = FuturableTask.of(() => fetchFromWebSocket(ws))
|
|
1008
|
+
* .onCancel(() => {
|
|
1009
|
+
* console.log('Closing WebSocket');
|
|
1010
|
+
* ws.close();
|
|
1011
|
+
* });
|
|
1012
|
+
*
|
|
1013
|
+
* task.cancel(); // Closes WebSocket even if never run
|
|
1014
|
+
* ```
|
|
1015
|
+
*/
|
|
1016
|
+
onCancel(cb: () => void): this;
|
|
1017
|
+
/**
|
|
1018
|
+
* Executes the task and returns a Futurable.
|
|
1019
|
+
*
|
|
1020
|
+
* Each call to run() creates a new independent execution. The returned Futurable
|
|
1021
|
+
* can be cancelled in two ways:
|
|
1022
|
+
* 1. By calling task.cancel() - cancels all running executions
|
|
1023
|
+
* 2. By calling futurable.cancel() - cancels only this specific execution
|
|
1024
|
+
* 3. By aborting the overrideSignal - cancels only this specific execution
|
|
1025
|
+
*
|
|
1026
|
+
* The execution uses a composite signal that listens to:
|
|
1027
|
+
* - The task's internal signal (from task.cancel())
|
|
1028
|
+
* - The overrideSignal (if provided)
|
|
1029
|
+
*
|
|
1030
|
+
* If the task is already cancelled, the returned Futurable will be pending (never resolves).
|
|
1031
|
+
*
|
|
1032
|
+
* @param overrideSignal - Optional signal to override/supplement the task's default signal.
|
|
1033
|
+
* Useful for adding execution-specific cancellation.
|
|
1034
|
+
* @returns A Futurable representing this execution
|
|
1035
|
+
*
|
|
1036
|
+
* @example
|
|
1037
|
+
* ```typescript
|
|
1038
|
+
* const task = FuturableTask.of(() => fetch('/data'));
|
|
1039
|
+
*
|
|
1040
|
+
* const run1 = task.run(); // Can be cancelled by task.cancel() or run1.cancel()
|
|
1041
|
+
* const run2 = task.run(); // Independent execution
|
|
1042
|
+
*
|
|
1043
|
+
* task.cancel(); // Cancels both run1 and run2
|
|
1044
|
+
* ```
|
|
1045
|
+
*
|
|
1046
|
+
* @example
|
|
1047
|
+
* ```typescript
|
|
1048
|
+
* // With override signal
|
|
1049
|
+
* const task = FuturableTask.of(() => fetch('/data'));
|
|
1050
|
+
* const controller = new AbortController();
|
|
1051
|
+
*
|
|
1052
|
+
* const run = task.run(controller.signal);
|
|
1053
|
+
*
|
|
1054
|
+
* controller.abort(); // Cancels only this execution
|
|
1055
|
+
* // task itself is NOT cancelled, can still run() again
|
|
1056
|
+
* ```
|
|
1057
|
+
*
|
|
1058
|
+
* @example
|
|
1059
|
+
* ```typescript
|
|
1060
|
+
* // Cancelled task produces pending Futurables
|
|
1061
|
+
* const task = FuturableTask.of(() => fetch('/data'));
|
|
1062
|
+
* task.cancel();
|
|
1063
|
+
*
|
|
1064
|
+
* const run = task.run(); // Never resolves or rejects (pending)
|
|
1065
|
+
* ```
|
|
1066
|
+
*/
|
|
1067
|
+
run(overrideSignal?: AbortSignal): Futurable<T>;
|
|
1068
|
+
/**
|
|
1069
|
+
* Executes the task and returns a SafeResult instead of throwing errors.
|
|
1070
|
+
*
|
|
1071
|
+
* This method wraps the task execution in a try-catch pattern that returns
|
|
1072
|
+
* a discriminated union type, making error handling explicit and type-safe.
|
|
1073
|
+
*
|
|
1074
|
+
* The returned Futurable resolves to a SafeResult object that contains either:
|
|
1075
|
+
* - `{ success: true, data: T, error: null }` on success
|
|
1076
|
+
* - `{ success: false, data: null, error: E }` on failure
|
|
1077
|
+
*
|
|
1078
|
+
* This pattern eliminates the need for try-catch blocks and makes error
|
|
1079
|
+
* handling explicit at the type level, similar to Rust's Result type.
|
|
1080
|
+
*
|
|
1081
|
+
* @template E - The type of error (defaults to unknown)
|
|
1082
|
+
* @param overrideSignal - Optional signal to override/supplement the task's default signal
|
|
1083
|
+
* @returns A Futurable that always resolves with a SafeResult (never rejects)
|
|
1084
|
+
*
|
|
1085
|
+
* @example
|
|
1086
|
+
* ```typescript
|
|
1087
|
+
* const task = FuturableTask.of(() => riskyOperation());
|
|
1088
|
+
* const result = await task.runSafe();
|
|
1089
|
+
*
|
|
1090
|
+
* if (result.success) {
|
|
1091
|
+
* console.log('Success:', result.data);
|
|
1092
|
+
* // TypeScript knows result.data is T
|
|
1093
|
+
* } else {
|
|
1094
|
+
* console.error('Error:', result.error);
|
|
1095
|
+
* // TypeScript knows result.error is E
|
|
1096
|
+
* }
|
|
1097
|
+
* ```
|
|
1098
|
+
*
|
|
1099
|
+
* @example
|
|
1100
|
+
* ```typescript
|
|
1101
|
+
* // Chaining multiple safe operations
|
|
1102
|
+
* const result1 = await task1.runSafe();
|
|
1103
|
+
* if (!result1.success) return result1.error;
|
|
1104
|
+
*
|
|
1105
|
+
* const result2 = await task2.runSafe();
|
|
1106
|
+
* if (!result2.success) return result2.error;
|
|
1107
|
+
*
|
|
1108
|
+
* return { data1: result1.data, data2: result2.data };
|
|
1109
|
+
* ```
|
|
1110
|
+
*
|
|
1111
|
+
* @example
|
|
1112
|
+
* ```typescript
|
|
1113
|
+
* // With explicit error type
|
|
1114
|
+
* const task = FuturableTask.of(() => fetchUser(id));
|
|
1115
|
+
* const result = await task.runSafe<ApiError>();
|
|
1116
|
+
*
|
|
1117
|
+
* if (!result.success) {
|
|
1118
|
+
* // result.error is typed as ApiError
|
|
1119
|
+
* console.error('API Error:', result.error.statusCode);
|
|
1120
|
+
* }
|
|
1121
|
+
* ```
|
|
1122
|
+
*
|
|
1123
|
+
* @example
|
|
1124
|
+
* ```typescript
|
|
1125
|
+
* // Functional error handling without exceptions
|
|
1126
|
+
* const results = await Promise.all([
|
|
1127
|
+
* task1.runSafe(),
|
|
1128
|
+
* task2.runSafe(),
|
|
1129
|
+
* task3.runSafe()
|
|
1130
|
+
* ]);
|
|
1131
|
+
*
|
|
1132
|
+
* const errors = results.filter(r => !r.success);
|
|
1133
|
+
* const successes = results.filter(r => r.success);
|
|
1134
|
+
*
|
|
1135
|
+
* console.log(`${successes.length} succeeded, ${errors.length} failed`);
|
|
1136
|
+
* ```
|
|
1137
|
+
*
|
|
1138
|
+
* @example
|
|
1139
|
+
* ```typescript
|
|
1140
|
+
* // With cancellation support
|
|
1141
|
+
* const controller = new AbortController();
|
|
1142
|
+
* const result = await task.runSafe(controller.signal);
|
|
1143
|
+
*
|
|
1144
|
+
* // Can be cancelled externally
|
|
1145
|
+
* controller.abort();
|
|
1146
|
+
* ```
|
|
1147
|
+
*/
|
|
1148
|
+
runSafe<E = unknown>(overrideSignal?: AbortSignal): Futurable<SafeResult<T, E>>;
|
|
1149
|
+
/**
|
|
1150
|
+
* Caches the result of the first execution and reuses it for subsequent runs.
|
|
1151
|
+
*
|
|
1152
|
+
* All calls to run() after the first will return the same cached Futurable.
|
|
1153
|
+
* This is useful for expensive computations that should only run once.
|
|
1154
|
+
*
|
|
1155
|
+
* IMPORTANT: The cached result is shared across all calls. If you need independent
|
|
1156
|
+
* executions, don't use memoize() or create a new memoized task for each use case.
|
|
1157
|
+
*
|
|
1158
|
+
* Returns a NEW FuturableTask (does not mutate the original).
|
|
1159
|
+
*
|
|
1160
|
+
* @param catchErrors - If true, caches the result even when the execution rejects.
|
|
1161
|
+
* If false (default), a rejection clears the cache and the next
|
|
1162
|
+
* run() will retry the operation.
|
|
1163
|
+
* @returns A new FuturableTask that caches its result
|
|
1164
|
+
*
|
|
1165
|
+
* @example
|
|
1166
|
+
* ```typescript
|
|
1167
|
+
* const expensiveTask = FuturableTask.of(() => {
|
|
1168
|
+
* console.log('Computing...');
|
|
1169
|
+
* return complexCalculation();
|
|
1170
|
+
* }).memoize();
|
|
1171
|
+
*
|
|
1172
|
+
* await expensiveTask.run(); // Logs "Computing..." and calculates
|
|
1173
|
+
* await expensiveTask.run(); // Returns cached result immediately (no log)
|
|
1174
|
+
* await expensiveTask.run(); // Returns cached result immediately (no log)
|
|
1175
|
+
* ```
|
|
1176
|
+
*
|
|
1177
|
+
* @example
|
|
1178
|
+
* ```typescript
|
|
1179
|
+
* // Without catchErrors (default) - retries on failure
|
|
1180
|
+
* const task = FuturableTask.of(() => riskyOperation()).memoize();
|
|
1181
|
+
*
|
|
1182
|
+
* try {
|
|
1183
|
+
* await task.run(); // Fails
|
|
1184
|
+
* } catch (err) {}
|
|
1185
|
+
*
|
|
1186
|
+
* await task.run(); // Retries (not cached because it failed)
|
|
1187
|
+
* ```
|
|
1188
|
+
*
|
|
1189
|
+
* @example
|
|
1190
|
+
* ```typescript
|
|
1191
|
+
* // With catchErrors - caches failures too
|
|
1192
|
+
* const task = FuturableTask.of(() => riskyOperation()).memoize(true);
|
|
1193
|
+
*
|
|
1194
|
+
* try {
|
|
1195
|
+
* await task.run(); // Fails
|
|
1196
|
+
* } catch (err) {}
|
|
1197
|
+
*
|
|
1198
|
+
* try {
|
|
1199
|
+
* await task.run(); // Returns cached error (doesn't retry)
|
|
1200
|
+
* } catch (err) {}
|
|
1201
|
+
* ```
|
|
1202
|
+
*/
|
|
1203
|
+
memoize(catchErrors?: boolean): FuturableTask<T>;
|
|
1204
|
+
/**
|
|
1205
|
+
* Transforms the task's result value using a mapping function.
|
|
1206
|
+
*
|
|
1207
|
+
* The transformation is lazy and won't execute until run() is called.
|
|
1208
|
+
* The mapping function receives the resolved value and optionally the abort signal
|
|
1209
|
+
* to check if the operation was cancelled.
|
|
1210
|
+
*
|
|
1211
|
+
* This is the basic building block for transforming task results.
|
|
1212
|
+
*
|
|
1213
|
+
* @template U - The type of the transformed value
|
|
1214
|
+
* @param fn - Function to transform the value. Can be sync or async.
|
|
1215
|
+
* Receives the value and optionally the signal.
|
|
1216
|
+
* @returns A new FuturableTask with the transformed value
|
|
1217
|
+
*
|
|
1218
|
+
* @example
|
|
1219
|
+
* ```typescript
|
|
1220
|
+
* const doubleTask = FuturableTask.resolve(5)
|
|
1221
|
+
* .map(x => x * 2);
|
|
1222
|
+
*
|
|
1223
|
+
* await doubleTask.run(); // 10
|
|
1224
|
+
* ```
|
|
1225
|
+
*
|
|
1226
|
+
* @example
|
|
1227
|
+
* ```typescript
|
|
1228
|
+
* // Async transformation
|
|
1229
|
+
* const task = FuturableTask.of(() => fetch('/users'))
|
|
1230
|
+
* .map(async res => await res.json())
|
|
1231
|
+
* .map(users => users.filter(u => u.active));
|
|
1232
|
+
*
|
|
1233
|
+
* const activeUsers = await task.run();
|
|
1234
|
+
* ```
|
|
1235
|
+
*
|
|
1236
|
+
* @example
|
|
1237
|
+
* ```typescript
|
|
1238
|
+
* // Using the signal parameter
|
|
1239
|
+
* const task = FuturableTask.of(() => fetchData())
|
|
1240
|
+
* .map((data, signal) => {
|
|
1241
|
+
* if (signal?.aborted) {
|
|
1242
|
+
* console.log('Mapping cancelled');
|
|
1243
|
+
* return null;
|
|
1244
|
+
* }
|
|
1245
|
+
* return expensiveTransformation(data);
|
|
1246
|
+
* });
|
|
1247
|
+
* ```
|
|
1248
|
+
*/
|
|
1249
|
+
map<U>(fn: (data: T, signal?: AbortSignal) => U | Promise<U>): FuturableTask<U>;
|
|
1250
|
+
/**
|
|
1251
|
+
* Chains this task with another task, creating a sequential composition.
|
|
1252
|
+
*
|
|
1253
|
+
* Also known as "bind" or "chain" in functional programming.
|
|
1254
|
+
* The function receives the resolved value and must return a new FuturableTask.
|
|
1255
|
+
*
|
|
1256
|
+
* This allows you to create dependent computations where the next task
|
|
1257
|
+
* depends on the result of the previous one.
|
|
1258
|
+
*
|
|
1259
|
+
* @template U - The type of value the chained task produces
|
|
1260
|
+
* @param fn - Function that receives the value and returns a new FuturableTask
|
|
1261
|
+
* @returns A new FuturableTask representing the chained computation
|
|
1262
|
+
*
|
|
1263
|
+
* @example
|
|
1264
|
+
* ```typescript
|
|
1265
|
+
* const getUserTask = FuturableTask.of(() => fetch('/user/1').then(r => r.json()));
|
|
1266
|
+
* const getPostsTask = (user) => FuturableTask.of(() =>
|
|
1267
|
+
* fetch(`/users/${user.id}/posts`).then(r => r.json())
|
|
1268
|
+
* );
|
|
1269
|
+
*
|
|
1270
|
+
* const userPosts = getUserTask.flatMap(getPostsTask);
|
|
1271
|
+
* const posts = await userPosts.run();
|
|
1272
|
+
* ```
|
|
1273
|
+
*
|
|
1274
|
+
* @example
|
|
1275
|
+
* ```typescript
|
|
1276
|
+
* // Chaining multiple dependent operations
|
|
1277
|
+
* const result = await FuturableTask.of(() => readConfig())
|
|
1278
|
+
* .flatMap(config => FuturableTask.of(() => connectToDb(config)))
|
|
1279
|
+
* .flatMap(db => FuturableTask.of(() => db.query('SELECT * FROM users')))
|
|
1280
|
+
* .run();
|
|
1281
|
+
* ```
|
|
1282
|
+
*
|
|
1283
|
+
* @example
|
|
1284
|
+
* ```typescript
|
|
1285
|
+
* // Error handling in chains
|
|
1286
|
+
* const result = await FuturableTask.of(() => mayFail())
|
|
1287
|
+
* .flatMap(val => {
|
|
1288
|
+
* if (val < 0) {
|
|
1289
|
+
* return FuturableTask.reject(new Error('Negative value'));
|
|
1290
|
+
* }
|
|
1291
|
+
* return FuturableTask.resolve(val * 2);
|
|
1292
|
+
* })
|
|
1293
|
+
* .run();
|
|
1294
|
+
* ```
|
|
1295
|
+
*/
|
|
1296
|
+
flatMap<U>(fn: (data: T) => FuturableTask<U>): FuturableTask<U>;
|
|
1297
|
+
/**
|
|
1298
|
+
* Sequences this task with another task, discarding the first result.
|
|
1299
|
+
*
|
|
1300
|
+
* Executes the current task, waits for it to complete, then executes the next task.
|
|
1301
|
+
* The result of the current task is ignored - only the next task's result is returned.
|
|
1302
|
+
*
|
|
1303
|
+
* Useful for sequencing side effects or setup operations before the main computation.
|
|
1304
|
+
*
|
|
1305
|
+
* @template U - The type of value the next task produces
|
|
1306
|
+
* @param nextTask - The task to execute after this one
|
|
1307
|
+
* @returns A new FuturableTask that produces the next task's result
|
|
1308
|
+
*
|
|
1309
|
+
* @example
|
|
1310
|
+
* ```typescript
|
|
1311
|
+
* const setupTask = FuturableTask.of(() => initializeApp());
|
|
1312
|
+
* const mainTask = FuturableTask.of(() => fetchData());
|
|
1313
|
+
*
|
|
1314
|
+
* const result = await setupTask.andThen(mainTask).run();
|
|
1315
|
+
* // setupTask runs first, then mainTask, result is from mainTask
|
|
1316
|
+
* ```
|
|
1317
|
+
*
|
|
1318
|
+
* @example
|
|
1319
|
+
* ```typescript
|
|
1320
|
+
* // Chaining multiple sequential tasks
|
|
1321
|
+
* await FuturableTask.of(() => clearCache())
|
|
1322
|
+
* .andThen(FuturableTask.of(() => warmupCache()))
|
|
1323
|
+
* .andThen(FuturableTask.of(() => startServer()))
|
|
1324
|
+
* .run();
|
|
1325
|
+
* ```
|
|
1326
|
+
*/
|
|
1327
|
+
andThen<U>(nextTask: FuturableTask<U>): FuturableTask<U>;
|
|
1328
|
+
/**
|
|
1329
|
+
* Executes a side-effect function with the task's value without modifying it.
|
|
1330
|
+
*
|
|
1331
|
+
* The value passes through unchanged. Useful for logging, debugging, or
|
|
1332
|
+
* triggering actions based on the value without affecting the result.
|
|
1333
|
+
*
|
|
1334
|
+
* If the side-effect function throws or rejects, the error is propagated.
|
|
1335
|
+
*
|
|
1336
|
+
* @param fn - Side-effect function that receives the value. Can be sync or async.
|
|
1337
|
+
* @returns A new FuturableTask that passes through the original value
|
|
1338
|
+
*
|
|
1339
|
+
* @example
|
|
1340
|
+
* ```typescript
|
|
1341
|
+
* const task = FuturableTask.of(() => fetchUser())
|
|
1342
|
+
* .tap(user => console.log('Fetched user:', user.id))
|
|
1343
|
+
* .map(user => user.name);
|
|
1344
|
+
*
|
|
1345
|
+
* const name = await task.run(); // Logs, then returns name
|
|
1346
|
+
* ```
|
|
1347
|
+
*
|
|
1348
|
+
* @example
|
|
1349
|
+
* ```typescript
|
|
1350
|
+
* // Multiple taps for debugging
|
|
1351
|
+
* const result = await FuturableTask.of(() => calculateResult())
|
|
1352
|
+
* .tap(val => console.log('Initial:', val))
|
|
1353
|
+
* .map(val => val * 2)
|
|
1354
|
+
* .tap(val => console.log('After doubling:', val))
|
|
1355
|
+
* .map(val => val + 10)
|
|
1356
|
+
* .tap(val => console.log('Final:', val))
|
|
1357
|
+
* .run();
|
|
1358
|
+
* ```
|
|
1359
|
+
*
|
|
1360
|
+
* @example
|
|
1361
|
+
* ```typescript
|
|
1362
|
+
* // Side effects with external systems
|
|
1363
|
+
* await FuturableTask.of(() => processData())
|
|
1364
|
+
* .tap(async data => {
|
|
1365
|
+
* await logToAnalytics('data_processed', data);
|
|
1366
|
+
* })
|
|
1367
|
+
* .run();
|
|
1368
|
+
* ```
|
|
1369
|
+
*/
|
|
1370
|
+
tap(fn: (data: T) => any): FuturableTask<T>;
|
|
1371
|
+
/**
|
|
1372
|
+
* Executes a side effect only if this task fails.
|
|
1373
|
+
*
|
|
1374
|
+
* The error is still propagated after the side effect executes.
|
|
1375
|
+
* Useful for error logging or monitoring without handling the error.
|
|
1376
|
+
*
|
|
1377
|
+
* If the side-effect function itself throws, that error is logged to console
|
|
1378
|
+
* but the original error is still propagated.
|
|
1379
|
+
*
|
|
1380
|
+
* @param fn - Function to execute on error. Can be sync or async.
|
|
1381
|
+
* @returns A new FuturableTask that passes through the original error
|
|
1382
|
+
*
|
|
1383
|
+
* @example
|
|
1384
|
+
* ```typescript
|
|
1385
|
+
* await apiTask
|
|
1386
|
+
* .tapError(error => logger.error('API failed', error))
|
|
1387
|
+
* .tapError(error => sendToSentry(error))
|
|
1388
|
+
* .catch(handleError);
|
|
1389
|
+
* ```
|
|
1390
|
+
*
|
|
1391
|
+
* @example
|
|
1392
|
+
* ```typescript
|
|
1393
|
+
* // Log errors without stopping propagation
|
|
1394
|
+
* try {
|
|
1395
|
+
* await FuturableTask.of(() => riskyOperation())
|
|
1396
|
+
* .tapError(err => console.error('Operation failed:', err))
|
|
1397
|
+
* .run();
|
|
1398
|
+
* } catch (err) {
|
|
1399
|
+
* // err is the original error
|
|
1400
|
+
* console.log('Caught:', err);
|
|
1401
|
+
* }
|
|
1402
|
+
* ```
|
|
1403
|
+
*
|
|
1404
|
+
* @example
|
|
1405
|
+
* ```typescript
|
|
1406
|
+
* const result = await FuturableTask.of(() => mayFail())
|
|
1407
|
+
* .tapError(err => analytics.trackError(err))
|
|
1408
|
+
* .orElse(err => FuturableTask.resolve(defaultValue))
|
|
1409
|
+
* .run();
|
|
1410
|
+
* ```
|
|
1411
|
+
*/
|
|
1412
|
+
tapError(fn: (error: any) => any): FuturableTask<T>;
|
|
1413
|
+
/**
|
|
1414
|
+
* Handles errors from the task execution by providing a fallback task.
|
|
1415
|
+
*
|
|
1416
|
+
* If the task succeeds, the result passes through unchanged.
|
|
1417
|
+
* If the task fails, the fallback function is called with the error
|
|
1418
|
+
* and must return a new FuturableTask to execute instead.
|
|
1419
|
+
*
|
|
1420
|
+
* The fallback task can return a different type, allowing for type transformations
|
|
1421
|
+
* during error fallbackToy.
|
|
1422
|
+
*
|
|
1423
|
+
* @template U - The type of the fallbackToy value
|
|
1424
|
+
* @param fallbackTask - Function that receives the error and returns a FuturableTask
|
|
1425
|
+
* @returns A new FuturableTask with error handling
|
|
1426
|
+
*
|
|
1427
|
+
* @example
|
|
1428
|
+
* ```typescript
|
|
1429
|
+
* const task = FuturableTask.of(() => fetchFromPrimary())
|
|
1430
|
+
* .catchError(err => {
|
|
1431
|
+
* console.error('Primary failed:', err);
|
|
1432
|
+
* return FuturableTask.of(() => fetchFromBackup());
|
|
1433
|
+
* });
|
|
1434
|
+
*
|
|
1435
|
+
* const data = await task.run(); // Falls back to backup on error
|
|
1436
|
+
* ```
|
|
1437
|
+
*
|
|
1438
|
+
* @example
|
|
1439
|
+
* ```typescript
|
|
1440
|
+
* // Retry with exponential backoff
|
|
1441
|
+
* const task = FuturableTask.of(() => unstableOperation())
|
|
1442
|
+
* .catchError(err =>
|
|
1443
|
+
* FuturableTask.delay(1000)
|
|
1444
|
+
* .flatMap(() => FuturableTask.of(() => unstableOperation()))
|
|
1445
|
+
* );
|
|
1446
|
+
* ```
|
|
1447
|
+
*
|
|
1448
|
+
* @example
|
|
1449
|
+
* ```typescript
|
|
1450
|
+
* // Type transformation on error
|
|
1451
|
+
* const task: FuturableTask<User> = fetchUser()
|
|
1452
|
+
* .catchError((err): FuturableTask<User | null> => {
|
|
1453
|
+
* if (err.status === 404) {
|
|
1454
|
+
* return FuturableTask.resolve(null);
|
|
1455
|
+
* }
|
|
1456
|
+
* return FuturableTask.reject(err);
|
|
1457
|
+
* });
|
|
1458
|
+
* ```
|
|
1459
|
+
*/
|
|
1460
|
+
catchError<U>(fallbackTask: (err: any) => FuturableTask<U>): FuturableTask<T | U>;
|
|
1461
|
+
/**
|
|
1462
|
+
* Provides an alternative FuturableTask to execute if the current one fails.
|
|
1463
|
+
*
|
|
1464
|
+
* Similar to catchError, but ensures the result type remains consistent (type T).
|
|
1465
|
+
* This is used for fallback logic like using cache if API fails.
|
|
1466
|
+
*
|
|
1467
|
+
* If the task succeeds, the result passes through unchanged.
|
|
1468
|
+
* If the task fails, the fallback function is called with the error.
|
|
1469
|
+
*
|
|
1470
|
+
* @param fallbackTask - A function that receives the error and returns a fallback FuturableTask
|
|
1471
|
+
* @returns A new FuturableTask that will attempt the alternative on failure
|
|
1472
|
+
*
|
|
1473
|
+
* @example
|
|
1474
|
+
* ```typescript
|
|
1475
|
+
* const task = FuturableTask.of(() => readLocalConfig())
|
|
1476
|
+
* .orElse(err => {
|
|
1477
|
+
* console.warn('Local config not found, loading default...');
|
|
1478
|
+
* return FuturableTask.of(() => readDefaultConfig());
|
|
1479
|
+
* });
|
|
1480
|
+
*
|
|
1481
|
+
* const config = await task.run();
|
|
1482
|
+
* ```
|
|
1483
|
+
*
|
|
1484
|
+
* @example
|
|
1485
|
+
* ```typescript
|
|
1486
|
+
* // Cache fallback
|
|
1487
|
+
* const task = FuturableTask.of(() => fetch('/api/data'))
|
|
1488
|
+
* .orElse(() => FuturableTask.of(() => loadFromCache()));
|
|
1489
|
+
* ```
|
|
1490
|
+
*
|
|
1491
|
+
* @example
|
|
1492
|
+
* ```typescript
|
|
1493
|
+
* // Multiple fallbacks
|
|
1494
|
+
* const task = FuturableTask.of(() => fetchFromPrimary())
|
|
1495
|
+
* .orElse(() => FuturableTask.of(() => fetchFromSecondary()))
|
|
1496
|
+
* .orElse(() => FuturableTask.of(() => fetchFromTertiary()));
|
|
1497
|
+
* ```
|
|
1498
|
+
*/
|
|
1499
|
+
orElse<U>(fallbackTask: (err: any) => FuturableTask<T | U>): FuturableTask<T | U>;
|
|
1500
|
+
/**
|
|
1501
|
+
* Provides a default value if the task fails.
|
|
1502
|
+
*
|
|
1503
|
+
* Simpler alternative to orElse when you just want to return a static value on failure.
|
|
1504
|
+
* The fallback value is wrapped in a resolved task automatically.
|
|
1505
|
+
*
|
|
1506
|
+
* @param fallback - The default value to use if the task fails
|
|
1507
|
+
* @returns A new FuturableTask that won't fail (returns fallback instead)
|
|
1508
|
+
*
|
|
1509
|
+
* @example
|
|
1510
|
+
* ```typescript
|
|
1511
|
+
* const data = await FuturableTask.of(() => fetchData())
|
|
1512
|
+
* .fallbackTo([])
|
|
1513
|
+
* .run();
|
|
1514
|
+
* // Always succeeds, returns [] if fetch fails
|
|
1515
|
+
* ```
|
|
1516
|
+
*
|
|
1517
|
+
* @example
|
|
1518
|
+
* ```typescript
|
|
1519
|
+
* // Configuration with defaults
|
|
1520
|
+
* const config = await FuturableTask.of(() => loadUserConfig())
|
|
1521
|
+
* .fallbackTo(DEFAULT_CONFIG)
|
|
1522
|
+
* .run();
|
|
1523
|
+
* ```
|
|
1524
|
+
*
|
|
1525
|
+
* @example
|
|
1526
|
+
* ```typescript
|
|
1527
|
+
* // Nullish values
|
|
1528
|
+
* const user = await FuturableTask.of(() => fetchUser(id))
|
|
1529
|
+
* .fallbackTo(null)
|
|
1530
|
+
* .run();
|
|
1531
|
+
* // Returns null instead of throwing on error
|
|
1532
|
+
* ```
|
|
1533
|
+
*/
|
|
1534
|
+
fallbackTo<U>(fallback: U): FuturableTask<T | U>;
|
|
1535
|
+
/**
|
|
1536
|
+
* Conditional branching based on the task's result.
|
|
1537
|
+
*
|
|
1538
|
+
* Evaluates a condition with the resolved value and executes one of two alternative
|
|
1539
|
+
* tasks based on the result. Both branches must return tasks of the same type.
|
|
1540
|
+
*
|
|
1541
|
+
* The condition can be async, allowing for asynchronous decision-making.
|
|
1542
|
+
*
|
|
1543
|
+
* @template U - The type returned by both branches
|
|
1544
|
+
* @param condition - Predicate function to evaluate (can be async)
|
|
1545
|
+
* @param onTrue - Task to execute if condition is true
|
|
1546
|
+
* @param onFalse - Task to execute if condition is false
|
|
1547
|
+
* @returns A new FuturableTask with conditional execution
|
|
1548
|
+
*
|
|
1549
|
+
* @example
|
|
1550
|
+
* ```typescript
|
|
1551
|
+
* const result = await userTask.ifElse(
|
|
1552
|
+
* user => user.isPremium,
|
|
1553
|
+
* user => FuturableTask.of(() => loadPremiumContent(user)),
|
|
1554
|
+
* user => FuturableTask.of(() => loadBasicContent(user))
|
|
1555
|
+
* ).run();
|
|
1556
|
+
* ```
|
|
1557
|
+
*
|
|
1558
|
+
* @example
|
|
1559
|
+
* ```typescript
|
|
1560
|
+
* // Async condition
|
|
1561
|
+
* const task = FuturableTask.of(() => getUser())
|
|
1562
|
+
* .ifElse(
|
|
1563
|
+
* async user => await hasPermission(user, 'admin'),
|
|
1564
|
+
* user => FuturableTask.of(() => adminDashboard(user)),
|
|
1565
|
+
* user => FuturableTask.of(() => userDashboard(user))
|
|
1566
|
+
* );
|
|
1567
|
+
* ```
|
|
1568
|
+
*
|
|
1569
|
+
* @example
|
|
1570
|
+
* ```typescript
|
|
1571
|
+
* // Validation with branching
|
|
1572
|
+
* const processed = await FuturableTask.of(() => parseInput(data))
|
|
1573
|
+
* .ifElse(
|
|
1574
|
+
* parsed => parsed.isValid,
|
|
1575
|
+
* parsed => FuturableTask.of(() => processValidData(parsed)),
|
|
1576
|
+
* parsed => FuturableTask.reject(new Error('Invalid data'))
|
|
1577
|
+
* )
|
|
1578
|
+
* .run();
|
|
1579
|
+
* ```
|
|
1580
|
+
*/
|
|
1581
|
+
ifElse<U>(condition: (value: T) => boolean | Promise<boolean>, onTrue: (value: T) => FuturableTask<U>, onFalse: (value: T) => FuturableTask<U>): FuturableTask<U>;
|
|
1582
|
+
/**
|
|
1583
|
+
* Folds the result of the task execution into a single value by applying
|
|
1584
|
+
* the appropriate transformation function.
|
|
1585
|
+
*
|
|
1586
|
+
* This is a catamorphism - it handles both success and failure cases uniformly,
|
|
1587
|
+
* mapping them both to the same result type. Both transformation functions must
|
|
1588
|
+
* return tasks of the same type.
|
|
1589
|
+
*
|
|
1590
|
+
* Useful when you want to treat success and failure symmetrically.
|
|
1591
|
+
*
|
|
1592
|
+
* @template U - The return type produced by the fold transformation
|
|
1593
|
+
* @param onFailure - Transformation function applied if the promise rejects
|
|
1594
|
+
* @param onSuccess - Transformation function applied if the promise resolves
|
|
1595
|
+
* @returns A new FuturableTask that resolves to the transformation result
|
|
1596
|
+
*
|
|
1597
|
+
* @example
|
|
1598
|
+
* ```typescript
|
|
1599
|
+
* const message = await FuturableTask.of(() => fetchData())
|
|
1600
|
+
* .fold(
|
|
1601
|
+
* error => FuturableTask.resolve(`Error: ${error.message}`),
|
|
1602
|
+
* data => FuturableTask.resolve(`Success: ${data.length} items`)
|
|
1603
|
+
* )
|
|
1604
|
+
* .run();
|
|
1605
|
+
* ```
|
|
1606
|
+
*
|
|
1607
|
+
* @example
|
|
1608
|
+
* ```typescript
|
|
1609
|
+
* // Result type for API responses
|
|
1610
|
+
* type Result<T> = { success: true; data: T } | { success: false; error: string };
|
|
1611
|
+
*
|
|
1612
|
+
* const result: Result<User> = await fetchUser()
|
|
1613
|
+
* .fold(
|
|
1614
|
+
* error => FuturableTask.resolve({ success: false, error: error.message }),
|
|
1615
|
+
* user => FuturableTask.resolve({ success: true, data: user })
|
|
1616
|
+
* )
|
|
1617
|
+
* .run();
|
|
1618
|
+
* ```
|
|
1619
|
+
*
|
|
1620
|
+
* @example
|
|
1621
|
+
* ```typescript
|
|
1622
|
+
* // Logging both outcomes
|
|
1623
|
+
* await task.fold(
|
|
1624
|
+
* err => FuturableTask.of(() => logError(err)).map(() => 0),
|
|
1625
|
+
* val => FuturableTask.of(() => logSuccess(val)).map(() => val)
|
|
1626
|
+
* ).run();
|
|
1627
|
+
* ```
|
|
1628
|
+
*/
|
|
1629
|
+
fold<U>(onFailure: (err: any) => FuturableTask<U>, onSuccess: (value: T) => FuturableTask<U>): FuturableTask<U>;
|
|
1630
|
+
/**
|
|
1631
|
+
* Registers a callback that runs when the task completes (success or failure).
|
|
1632
|
+
*
|
|
1633
|
+
* Similar to Promise.finally(). The callback cannot modify the result or error,
|
|
1634
|
+
* but is useful for cleanup operations like closing connections or hiding spinners.
|
|
1635
|
+
*
|
|
1636
|
+
* If the callback throws or rejects, that error is propagated.
|
|
1637
|
+
*
|
|
1638
|
+
* @param callback - Function to execute on completion (can be async)
|
|
1639
|
+
* @returns A new FuturableTask with the finally handler
|
|
1640
|
+
*
|
|
1641
|
+
* @example
|
|
1642
|
+
* ```typescript
|
|
1643
|
+
* const task = FuturableTask.of(() => fetchData())
|
|
1644
|
+
* .finally(() => {
|
|
1645
|
+
* console.log('Fetch completed');
|
|
1646
|
+
* hideSpinner();
|
|
1647
|
+
* });
|
|
1648
|
+
*
|
|
1649
|
+
* await task.run(); // Always hides spinner, even on error
|
|
1650
|
+
* ```
|
|
1651
|
+
*
|
|
1652
|
+
* @example
|
|
1653
|
+
* ```typescript
|
|
1654
|
+
* // Cleanup resources
|
|
1655
|
+
* const result = await FuturableTask.of(() => {
|
|
1656
|
+
* const connection = openConnection();
|
|
1657
|
+
* return processData(connection);
|
|
1658
|
+
* })
|
|
1659
|
+
* .finally(() => connection.close())
|
|
1660
|
+
* .run();
|
|
1661
|
+
* ```
|
|
1662
|
+
*
|
|
1663
|
+
* @example
|
|
1664
|
+
* ```typescript
|
|
1665
|
+
* // Multiple cleanup operations
|
|
1666
|
+
* await task
|
|
1667
|
+
* .finally(() => releaseResource1())
|
|
1668
|
+
* .finally(() => releaseResource2())
|
|
1669
|
+
* .finally(() => console.log('All cleanup done'))
|
|
1670
|
+
* .run();
|
|
1671
|
+
* ```
|
|
1672
|
+
*/
|
|
1673
|
+
finally(callback: () => any): FuturableTask<T>;
|
|
1674
|
+
/**
|
|
1675
|
+
* Adds a timeout to the task execution.
|
|
1676
|
+
*
|
|
1677
|
+
* If the task doesn't complete within the specified time, it's cancelled
|
|
1678
|
+
* and the task rejects with the provided reason.
|
|
1679
|
+
*
|
|
1680
|
+
* The timeout only applies when the task is run, not when it's created.
|
|
1681
|
+
*
|
|
1682
|
+
* @param ms - Timeout duration in milliseconds
|
|
1683
|
+
* @param reason - Rejection reason (default: "TimeoutExceeded")
|
|
1684
|
+
* @returns A new FuturableTask with timeout enforcement
|
|
1685
|
+
*
|
|
1686
|
+
* @example
|
|
1687
|
+
* ```typescript
|
|
1688
|
+
* const task = FuturableTask.of(() => fetch('/slow-api'))
|
|
1689
|
+
* .timeout(5000, new Error('Request timed out after 5s'));
|
|
1690
|
+
*
|
|
1691
|
+
* try {
|
|
1692
|
+
* await task.run();
|
|
1693
|
+
* } catch (err) {
|
|
1694
|
+
* console.error(err); // "Request timed out after 5s"
|
|
1695
|
+
* }
|
|
1696
|
+
* ```
|
|
1697
|
+
*
|
|
1698
|
+
* @example
|
|
1699
|
+
* ```typescript
|
|
1700
|
+
* // Timeout with default reason
|
|
1701
|
+
* await FuturableTask.of(() => longOperation())
|
|
1702
|
+
* .timeout(3000)
|
|
1703
|
+
* .run(); // Rejects with "TimeoutExceeded" after 3s
|
|
1704
|
+
* ```
|
|
1705
|
+
*
|
|
1706
|
+
* @example
|
|
1707
|
+
* ```typescript
|
|
1708
|
+
* // Chaining with retry
|
|
1709
|
+
* const result = await FuturableTask.of(() => fetchData())
|
|
1710
|
+
* .timeout(5000)
|
|
1711
|
+
* .retry(3)
|
|
1712
|
+
* .run();
|
|
1713
|
+
* // Each attempt has a 5s timeout
|
|
1714
|
+
* ```
|
|
1715
|
+
*/
|
|
1716
|
+
timeout(ms: number, reason?: any): FuturableTask<T>;
|
|
1717
|
+
/**
|
|
1718
|
+
* Delays the execution of the task by a specified duration.
|
|
1719
|
+
*
|
|
1720
|
+
* The timer starts when run() is called, not when delay() is called.
|
|
1721
|
+
* The task waits for the specified duration before executing.
|
|
1722
|
+
*
|
|
1723
|
+
* Useful for rate limiting, debouncing, or implementing backoff strategies.
|
|
1724
|
+
*
|
|
1725
|
+
* @param ms - Delay duration in milliseconds
|
|
1726
|
+
* @returns A new FuturableTask with the delay
|
|
1727
|
+
*
|
|
1728
|
+
* @example
|
|
1729
|
+
* ```typescript
|
|
1730
|
+
* const delayedTask = FuturableTask.of(() => sendEmail())
|
|
1731
|
+
* .delay(5000); // Wait 5 seconds before sending
|
|
1732
|
+
*
|
|
1733
|
+
* await delayedTask.run(); // Starts after 5s
|
|
1734
|
+
* ```
|
|
1735
|
+
*
|
|
1736
|
+
* @example
|
|
1737
|
+
* ```typescript
|
|
1738
|
+
* // Progressive delays
|
|
1739
|
+
* await FuturableTask.of(() => step1())
|
|
1740
|
+
* .andThen(FuturableTask.of(() => step2()).delay(1000))
|
|
1741
|
+
* .andThen(FuturableTask.of(() => step3()).delay(2000))
|
|
1742
|
+
* .run();
|
|
1743
|
+
* // step1 immediately, step2 after 1s, step3 after 2s
|
|
1744
|
+
* ```
|
|
1745
|
+
*
|
|
1746
|
+
* @example
|
|
1747
|
+
* ```typescript
|
|
1748
|
+
* // Cancellable delay
|
|
1749
|
+
* const task = FuturableTask.of(() => operation()).delay(10000);
|
|
1750
|
+
* const run = task.run();
|
|
1751
|
+
* task.cancel(); // Cancels before the delay completes
|
|
1752
|
+
* ```
|
|
1753
|
+
*/
|
|
1754
|
+
delay(ms: number): FuturableTask<T>;
|
|
1755
|
+
/**
|
|
1756
|
+
* Retries the task up to n times on failure.
|
|
1757
|
+
*
|
|
1758
|
+
* Each retry is an independent execution of the original task.
|
|
1759
|
+
* If all attempts fail, the last error is propagated.
|
|
1760
|
+
*
|
|
1761
|
+
* An optional delay can be added between retries for exponential backoff patterns.
|
|
1762
|
+
*
|
|
1763
|
+
* @param retries - Maximum number of retry attempts (0 means 1 total attempt)
|
|
1764
|
+
* @param delayMs - Optional delay between retries in milliseconds (default: 0)
|
|
1765
|
+
* @returns A new FuturableTask with retry logic
|
|
1766
|
+
*
|
|
1767
|
+
* @example
|
|
1768
|
+
* ```typescript
|
|
1769
|
+
* const unreliableTask = FuturableTask.of(() => fetch('/flaky-api'))
|
|
1770
|
+
* .retry(3); // Will try up to 4 times total (initial + 3 retries)
|
|
1771
|
+
*
|
|
1772
|
+
* const data = await unreliableTask.run();
|
|
1773
|
+
* ```
|
|
1774
|
+
*
|
|
1775
|
+
* @example
|
|
1776
|
+
* ```typescript
|
|
1777
|
+
* // With delay between retries
|
|
1778
|
+
* await FuturableTask.of(() => connectToService())
|
|
1779
|
+
* .retry(5, 1000) // Retry 5 times with 1s between attempts
|
|
1780
|
+
* .run();
|
|
1781
|
+
* ```
|
|
1782
|
+
*
|
|
1783
|
+
* @example
|
|
1784
|
+
* ```typescript
|
|
1785
|
+
* // Exponential backoff (manual)
|
|
1786
|
+
* let attempt = 0;
|
|
1787
|
+
* const task = FuturableTask.of(() => fetch('/api'))
|
|
1788
|
+
* .tapError(() => attempt++)
|
|
1789
|
+
* .retry(3, Math.pow(2, attempt) * 1000);
|
|
1790
|
+
* ```
|
|
1791
|
+
*/
|
|
1792
|
+
retry(retries: number, delayMs?: number): FuturableTask<T>;
|
|
1793
|
+
/**
|
|
1794
|
+
* Creates a new Task that delays the execution of the original task with debounce logic.
|
|
1795
|
+
*
|
|
1796
|
+
* This method implements "smart debounce":
|
|
1797
|
+
* - If called on a regular Task, it wraps it with a delay
|
|
1798
|
+
* - If called on an already debounced Task (e.g., `.debounce(200).debounce(300)`),
|
|
1799
|
+
* it overrides the previous delay instead of nesting, ensuring only the
|
|
1800
|
+
* latest delay is applied to the source task
|
|
1801
|
+
*
|
|
1802
|
+
* Multiple calls to run() within the debounce window will cancel previous
|
|
1803
|
+
* pending executions and restart the timer.
|
|
1804
|
+
*
|
|
1805
|
+
* Perfect for scenarios like search-as-you-type where you want to wait for
|
|
1806
|
+
* user input to stabilize before executing.
|
|
1807
|
+
*
|
|
1808
|
+
* @param ms - The debounce delay in milliseconds
|
|
1809
|
+
* @returns A new Task instance that manages the debounced execution
|
|
1810
|
+
*
|
|
1811
|
+
* @example
|
|
1812
|
+
* ```typescript
|
|
1813
|
+
* const searchTask = FuturableTask.of(() => searchAPI(query))
|
|
1814
|
+
* .debounce(300);
|
|
1815
|
+
*
|
|
1816
|
+
* // Rapidly calling run() multiple times:
|
|
1817
|
+
* searchTask.run(); // Cancelled
|
|
1818
|
+
* searchTask.run(); // Cancelled
|
|
1819
|
+
* searchTask.run(); // This one executes after 300ms
|
|
1820
|
+
* ```
|
|
1821
|
+
*
|
|
1822
|
+
* @example
|
|
1823
|
+
* ```typescript
|
|
1824
|
+
* // Smart debounce - latest delay wins
|
|
1825
|
+
* const task = FuturableTask.of(() => operation())
|
|
1826
|
+
* .debounce(200)
|
|
1827
|
+
* .debounce(500); // Uses 500ms, not 200ms + 500ms
|
|
1828
|
+
* ```
|
|
1829
|
+
*
|
|
1830
|
+
* @example
|
|
1831
|
+
* ```typescript
|
|
1832
|
+
* // Cancelling a debounced task
|
|
1833
|
+
* const task = FuturableTask.of(() => saveData()).debounce(1000);
|
|
1834
|
+
* task.run();
|
|
1835
|
+
* task.cancel(); // Cancels the pending execution
|
|
1836
|
+
* ```
|
|
1837
|
+
*/
|
|
1838
|
+
debounce(ms: number): FuturableTask<T>;
|
|
1839
|
+
/**
|
|
1840
|
+
* Creates a new Task that limits the execution rate to once every 'ms' milliseconds.
|
|
1841
|
+
*
|
|
1842
|
+
* Unlike debounce, throttle ensures the task runs at a steady maximum rate.
|
|
1843
|
+
* The first call executes immediately, and subsequent calls within the throttle
|
|
1844
|
+
* window will return the result of the previous execution.
|
|
1845
|
+
*
|
|
1846
|
+
* Perfect for performance-heavy operations that need to run consistently
|
|
1847
|
+
* during continuous events like scrolling, resizing, or mouse movement.
|
|
1848
|
+
*
|
|
1849
|
+
* @param ms - The throttle interval in milliseconds
|
|
1850
|
+
* @returns A new Task instance with throttling logic
|
|
1851
|
+
*
|
|
1852
|
+
* @example
|
|
1853
|
+
* ```typescript
|
|
1854
|
+
* const scrollTask = FuturableTask.of(() => updateScrollPosition())
|
|
1855
|
+
* .throttle(100);
|
|
1856
|
+
*
|
|
1857
|
+
* window.addEventListener('scroll', () => {
|
|
1858
|
+
* scrollTask.run(); // Runs at most once per 100ms
|
|
1859
|
+
* });
|
|
1860
|
+
* ```
|
|
1861
|
+
*
|
|
1862
|
+
* @example
|
|
1863
|
+
* ```typescript
|
|
1864
|
+
* // API rate limiting
|
|
1865
|
+
* const apiTask = FuturableTask.of(() => fetch('/api/data'))
|
|
1866
|
+
* .throttle(1000);
|
|
1867
|
+
*
|
|
1868
|
+
* // Rapid clicks will reuse the same result within 1s window
|
|
1869
|
+
* button.addEventListener('click', () => {
|
|
1870
|
+
* apiTask.run().then(data => updateUI(data));
|
|
1871
|
+
* });
|
|
1872
|
+
* ```
|
|
1873
|
+
*
|
|
1874
|
+
* @example
|
|
1875
|
+
* ```typescript
|
|
1876
|
+
* // Performance monitoring
|
|
1877
|
+
* const monitor = FuturableTask.of(() => collectMetrics())
|
|
1878
|
+
* .throttle(5000);
|
|
1879
|
+
*
|
|
1880
|
+
* setInterval(() => monitor.run(), 100); // Collects every 5s despite 100ms interval
|
|
1881
|
+
* ```
|
|
1882
|
+
*/
|
|
1883
|
+
throttle(ms: number): FuturableTask<T>;
|
|
1884
|
+
/**
|
|
1885
|
+
* Combines this task with another task into a tuple of both results.
|
|
1886
|
+
*
|
|
1887
|
+
* Both tasks execute in parallel and the result is a tuple [T, U].
|
|
1888
|
+
* If either task fails, the combined task fails.
|
|
1889
|
+
*
|
|
1890
|
+
* This is useful for combining independent operations that you need to perform together.
|
|
1891
|
+
*
|
|
1892
|
+
* @template U - The type of value the other task produces
|
|
1893
|
+
* @param other - The task to combine with this one
|
|
1894
|
+
* @returns A new FuturableTask that resolves with a tuple of both results
|
|
1895
|
+
*
|
|
1896
|
+
* @example
|
|
1897
|
+
* ```typescript
|
|
1898
|
+
* const userTask = FuturableTask.of(() => fetchUser());
|
|
1899
|
+
* const postsTask = FuturableTask.of(() => fetchPosts());
|
|
1900
|
+
*
|
|
1901
|
+
* const [user, posts] = await userTask.zip(postsTask).run();
|
|
1902
|
+
* ```
|
|
1903
|
+
*
|
|
1904
|
+
* @example
|
|
1905
|
+
* ```typescript
|
|
1906
|
+
* // Combining multiple independent operations
|
|
1907
|
+
* const combined = taskA
|
|
1908
|
+
* .zip(taskB)
|
|
1909
|
+
* .zip(taskC)
|
|
1910
|
+
* .run(); // [[A, B], C]
|
|
1911
|
+
* ```
|
|
1912
|
+
*
|
|
1913
|
+
* @example
|
|
1914
|
+
* ```typescript
|
|
1915
|
+
* // Error handling - any failure cancels all
|
|
1916
|
+
* try {
|
|
1917
|
+
* const [result1, result2] = await task1.zip(task2).run();
|
|
1918
|
+
* } catch (err) {
|
|
1919
|
+
* // Either task1 or task2 failed
|
|
1920
|
+
* }
|
|
1921
|
+
* ```
|
|
1922
|
+
*/
|
|
1923
|
+
zip<U>(other: FuturableTask<U>): FuturableTask<[T, U]>;
|
|
1924
|
+
/**
|
|
1925
|
+
* Combines this task with another task and applies a combining function.
|
|
1926
|
+
*
|
|
1927
|
+
* Similar to zip, but instead of returning a tuple, it applies a function
|
|
1928
|
+
* to both results to produce a single combined value.
|
|
1929
|
+
*
|
|
1930
|
+
* Both tasks execute in parallel.
|
|
1931
|
+
*
|
|
1932
|
+
* @template U - The type of value the other task produces
|
|
1933
|
+
* @template R - The type of the combined result
|
|
1934
|
+
* @param other - The task to combine with this one
|
|
1935
|
+
* @param fn - Function to combine both results
|
|
1936
|
+
* @returns A new FuturableTask with the combined result
|
|
1937
|
+
*
|
|
1938
|
+
* @example
|
|
1939
|
+
* ```typescript
|
|
1940
|
+
* const sum = await taskA.zipWith(taskB, (a, b) => a + b).run();
|
|
1941
|
+
* ```
|
|
1942
|
+
*
|
|
1943
|
+
* @example
|
|
1944
|
+
* ```typescript
|
|
1945
|
+
* // Combining user data
|
|
1946
|
+
* const fullProfile = await basicInfoTask.zipWith(
|
|
1947
|
+
* preferencesTask,
|
|
1948
|
+
* (basic, prefs) => ({ ...basic, preferences: prefs })
|
|
1949
|
+
* ).run();
|
|
1950
|
+
* ```
|
|
1951
|
+
*
|
|
1952
|
+
* @example
|
|
1953
|
+
* ```typescript
|
|
1954
|
+
* // Computing derived values
|
|
1955
|
+
* const average = await minTask.zipWith(
|
|
1956
|
+
* maxTask,
|
|
1957
|
+
* (min, max) => (min + max) / 2
|
|
1958
|
+
* ).run();
|
|
1959
|
+
* ```
|
|
1960
|
+
*/
|
|
1961
|
+
zipWith<U, R>(other: FuturableTask<U>, fn: (a: T, b: U) => R): FuturableTask<R>;
|
|
1962
|
+
/**
|
|
1963
|
+
* Maps both success and error outcomes to new values.
|
|
1964
|
+
*
|
|
1965
|
+
* Applies different transformation functions depending on whether the task
|
|
1966
|
+
* succeeds or fails. Unlike fold, the transformations are synchronous and
|
|
1967
|
+
* don't return tasks.
|
|
1968
|
+
*
|
|
1969
|
+
* Useful for normalizing success and error cases into a consistent format.
|
|
1970
|
+
*
|
|
1971
|
+
* @template U - The type of the transformed success value
|
|
1972
|
+
* @template V - The type of the transformed error value
|
|
1973
|
+
* @param onSuccess - Function to transform the success value
|
|
1974
|
+
* @param onError - Function to transform the error
|
|
1975
|
+
* @returns A new FuturableTask with transformed success/error
|
|
1976
|
+
*
|
|
1977
|
+
* @example
|
|
1978
|
+
* ```typescript
|
|
1979
|
+
* const task = FuturableTask.of(() => riskyOperation())
|
|
1980
|
+
* .bimap(
|
|
1981
|
+
* result => `Success: ${result}`,
|
|
1982
|
+
* error => new CustomError(error.message)
|
|
1983
|
+
* );
|
|
1984
|
+
* ```
|
|
1985
|
+
*
|
|
1986
|
+
* @example
|
|
1987
|
+
* ```typescript
|
|
1988
|
+
* // Normalizing API responses
|
|
1989
|
+
* const normalized = await fetchData()
|
|
1990
|
+
* .bimap(
|
|
1991
|
+
* data => ({ status: 'ok', data }),
|
|
1992
|
+
* err => ({ status: 'error', message: err.message })
|
|
1993
|
+
* )
|
|
1994
|
+
* .run();
|
|
1995
|
+
* ```
|
|
1996
|
+
*
|
|
1997
|
+
* @example
|
|
1998
|
+
* ```typescript
|
|
1999
|
+
* // Adding context to errors
|
|
2000
|
+
* await task.bimap(
|
|
2001
|
+
* val => val,
|
|
2002
|
+
* err => new Error(`Operation failed: ${err.message}`)
|
|
2003
|
+
* ).run();
|
|
2004
|
+
* ```
|
|
2005
|
+
*/
|
|
2006
|
+
bimap<U, V>(onSuccess: (value: T) => U, onError: (error: any) => V): FuturableTask<U>;
|
|
2007
|
+
/**
|
|
2008
|
+
* Repeats this task n times, collecting all results.
|
|
2009
|
+
*
|
|
2010
|
+
* Each execution is independent. If any execution fails, the entire
|
|
2011
|
+
* repeat operation fails.
|
|
2012
|
+
*
|
|
2013
|
+
* Executes sequentially, not in parallel.
|
|
2014
|
+
*
|
|
2015
|
+
* @param n - Number of times to repeat (must be >= 0)
|
|
2016
|
+
* @returns A new FuturableTask that resolves with an array of all results
|
|
2017
|
+
*
|
|
2018
|
+
* @example
|
|
2019
|
+
* ```typescript
|
|
2020
|
+
* const results = await task.repeat(3).run(); // [result1, result2, result3]
|
|
2021
|
+
* ```
|
|
2022
|
+
*
|
|
2023
|
+
* @example
|
|
2024
|
+
* ```typescript
|
|
2025
|
+
* // Collecting multiple samples
|
|
2026
|
+
* const measurements = await FuturableTask.of(() => takeMeasurement())
|
|
2027
|
+
* .repeat(10)
|
|
2028
|
+
* .map(samples => average(samples))
|
|
2029
|
+
* .run();
|
|
2030
|
+
* ```
|
|
2031
|
+
*
|
|
2032
|
+
* @example
|
|
2033
|
+
* ```typescript
|
|
2034
|
+
* // Batch operations
|
|
2035
|
+
* const created = await FuturableTask.of(() => createRecord())
|
|
2036
|
+
* .repeat(5)
|
|
2037
|
+
* .run();
|
|
2038
|
+
* ```
|
|
2039
|
+
*/
|
|
2040
|
+
repeat(n: number): FuturableTask<T[]>;
|
|
2041
|
+
/**
|
|
2042
|
+
* Composes multiple transformation functions in sequence.
|
|
2043
|
+
*
|
|
2044
|
+
* Applies transformations from left to right, passing the result of each
|
|
2045
|
+
* transformation to the next. Type-safe for up to 9 transformations.
|
|
2046
|
+
*
|
|
2047
|
+
* This is a powerful way to build complex task pipelines in a readable way.
|
|
2048
|
+
*
|
|
2049
|
+
* @param fns - Transformation functions to apply in order
|
|
2050
|
+
* @returns The result of applying all transformations
|
|
2051
|
+
*
|
|
2052
|
+
* @example
|
|
2053
|
+
* ```typescript
|
|
2054
|
+
* const addRetry = (task) => task.retry(3);
|
|
2055
|
+
* const addTimeout = (task) => task.timeout(5000);
|
|
2056
|
+
* const addLogging = (task) => task.tap(x => console.log(x));
|
|
2057
|
+
*
|
|
2058
|
+
* const enhanced = baseTask.pipe(addRetry, addTimeout, addLogging);
|
|
2059
|
+
* ```
|
|
2060
|
+
*
|
|
2061
|
+
* @example
|
|
2062
|
+
* ```typescript
|
|
2063
|
+
* // Building a processing pipeline
|
|
2064
|
+
* const processData = (task: FuturableTask<string>) => task
|
|
2065
|
+
* .pipe(
|
|
2066
|
+
* t => t.map(s => s.trim()),
|
|
2067
|
+
* t => t.map(s => s.toLowerCase()),
|
|
2068
|
+
* t => t.map(s => s.split(',')),
|
|
2069
|
+
* t => t.map(arr => arr.map(s => s.trim()))
|
|
2070
|
+
* );
|
|
2071
|
+
* ```
|
|
2072
|
+
*
|
|
2073
|
+
* @example
|
|
2074
|
+
* ```typescript
|
|
2075
|
+
* // Reusable middleware
|
|
2076
|
+
* const withErrorHandling = (task) => task.tapError(logError);
|
|
2077
|
+
* const withRetry = (task) => task.retry(3, 1000);
|
|
2078
|
+
* const withTimeout = (task) => task.timeout(10000);
|
|
2079
|
+
*
|
|
2080
|
+
* const robustTask = apiTask.pipe(
|
|
2081
|
+
* withErrorHandling,
|
|
2082
|
+
* withRetry,
|
|
2083
|
+
* withTimeout
|
|
2084
|
+
* );
|
|
2085
|
+
* ```
|
|
2086
|
+
*/
|
|
2087
|
+
pipe<A>(f1: (t: FuturableTask<T>) => A): A;
|
|
2088
|
+
pipe<A, B>(f1: (t: FuturableTask<T>) => A, f2: (a: A) => B): B;
|
|
2089
|
+
pipe<A, B, C>(f1: (t: FuturableTask<T>) => A, f2: (a: A) => B, f3: (b: B) => C): C;
|
|
2090
|
+
pipe<A, B, C, D>(f1: (t: FuturableTask<T>) => A, f2: (a: A) => B, f3: (b: B) => C, f4: (c: C) => D): D;
|
|
2091
|
+
pipe<A, B, C, D, E>(f1: (t: FuturableTask<T>) => A, f2: (a: A) => B, f3: (b: B) => C, f4: (c: C) => D, f5: (d: D) => E): E;
|
|
2092
|
+
pipe<A, B, C, D, E, F>(f1: (t: FuturableTask<T>) => A, f2: (a: A) => B, f3: (b: B) => C, f4: (c: C) => D, f5: (d: D) => E, f6: (e: E) => F): F;
|
|
2093
|
+
pipe<A, B, C, D, E, F, G>(f1: (t: FuturableTask<T>) => A, f2: (a: A) => B, f3: (b: B) => C, f4: (c: C) => D, f5: (d: D) => E, f6: (e: E) => F, f7: (f: F) => G): G;
|
|
2094
|
+
pipe<A, B, C, D, E, F, G, H>(f1: (t: FuturableTask<T>) => A, f2: (a: A) => B, f3: (b: B) => C, f4: (c: C) => D, f5: (d: D) => E, f6: (e: E) => F, f7: (f: F) => G, f8: (g: G) => H): H;
|
|
2095
|
+
pipe<A, B, C, D, E, F, G, H, I>(f1: (t: FuturableTask<T>) => A, f2: (a: A) => B, f3: (b: B) => C, f4: (c: C) => D, f5: (d: D) => E, f6: (e: E) => F, f7: (f: F) => G, f8: (g: G) => H, f9: (h: H) => I): I;
|
|
2096
|
+
/**
|
|
2097
|
+
* Creates a new FuturableTask that performs an HTTP fetch when executed.
|
|
2098
|
+
*
|
|
2099
|
+
* @param url - URL to fetch, or a function receiving the task's value
|
|
2100
|
+
* @param opts - Fetch options, or a function receiving the task's value
|
|
2101
|
+
* @returns A new FuturableTask that resolves with the Response
|
|
2102
|
+
*
|
|
2103
|
+
* @example
|
|
2104
|
+
* ```typescript
|
|
2105
|
+
* const fetchTask = FuturableTask.resolve('users')
|
|
2106
|
+
* .fetch(endpoint => `https://api.example.com/${endpoint}`);
|
|
2107
|
+
*
|
|
2108
|
+
* const response = await fetchTask.run();
|
|
2109
|
+
* const data = await response.json();
|
|
2110
|
+
* ```
|
|
2111
|
+
*/
|
|
2112
|
+
fetch(url: string | ((val: T) => string), opts?: RequestInit | ((val: T) => RequestInit)): FuturableTask<Response>;
|
|
2113
|
+
/**
|
|
2114
|
+
* Creates a FuturableTask from various input types.
|
|
2115
|
+
*
|
|
2116
|
+
* Accepts:
|
|
2117
|
+
* - Plain values: Wraps in a task that resolves immediately
|
|
2118
|
+
* - Functions: Creates a task that executes the function
|
|
2119
|
+
*
|
|
2120
|
+
* Functions receive FuturableUtils with signal, onCancel, delay, sleep, fetch, etc.
|
|
2121
|
+
*
|
|
2122
|
+
* @template U - Type of the value
|
|
2123
|
+
* @param input - A value or a function that returns a value/Promise
|
|
2124
|
+
* @param signal - Optional AbortSignal for the task
|
|
2125
|
+
* @returns A new FuturableTask
|
|
2126
|
+
*
|
|
2127
|
+
* @example
|
|
2128
|
+
* ```typescript
|
|
2129
|
+
* // From a value
|
|
2130
|
+
* const task1 = FuturableTask.of(42);
|
|
2131
|
+
* await task1.run(); // 42
|
|
2132
|
+
* ```
|
|
2133
|
+
*
|
|
2134
|
+
* @example
|
|
2135
|
+
* ```typescript
|
|
2136
|
+
* // From a function
|
|
2137
|
+
* const task2 = FuturableTask.of(() => fetch('/api').then(r => r.json()));
|
|
2138
|
+
* ```
|
|
2139
|
+
*
|
|
2140
|
+
* @example
|
|
2141
|
+
* ```typescript
|
|
2142
|
+
* // With utils
|
|
2143
|
+
* const task3 = FuturableTask.of(async (utils) => {
|
|
2144
|
+
* const response = await utils.fetch('/api/data');
|
|
2145
|
+
* return response.json();
|
|
2146
|
+
* });
|
|
2147
|
+
* ```
|
|
2148
|
+
*
|
|
2149
|
+
* @example
|
|
2150
|
+
* ```typescript
|
|
2151
|
+
* // With external signal
|
|
2152
|
+
* const controller = new AbortController();
|
|
2153
|
+
* const task = FuturableTask.of(() => operation(), controller.signal);
|
|
2154
|
+
* ```
|
|
2155
|
+
*/
|
|
2156
|
+
static of<U>(input: U | ((utils: FuturableUtils<U>) => Promise<U>), signal?: AbortSignal): FuturableTask<U>;
|
|
2157
|
+
/**
|
|
2158
|
+
* Creates a FuturableTask that immediately resolves with the given value.
|
|
2159
|
+
*
|
|
2160
|
+
* Alias for FuturableTask.of() when passing a plain value.
|
|
2161
|
+
* Useful for lifting values into the Task context.
|
|
2162
|
+
*
|
|
2163
|
+
* @template U - Type of the value
|
|
2164
|
+
* @param v - The value to resolve with
|
|
2165
|
+
* @param signal - Optional AbortSignal for the task
|
|
2166
|
+
* @returns A FuturableTask that resolves to the value
|
|
2167
|
+
*
|
|
2168
|
+
* @example
|
|
2169
|
+
* ```typescript
|
|
2170
|
+
* const task = FuturableTask.resolve(42)
|
|
2171
|
+
* .map(x => x * 2);
|
|
2172
|
+
*
|
|
2173
|
+
* await task.run(); // 84
|
|
2174
|
+
* ```
|
|
2175
|
+
*
|
|
2176
|
+
* @example
|
|
2177
|
+
* ```typescript
|
|
2178
|
+
* // Creating task chains
|
|
2179
|
+
* const pipeline = FuturableTask.resolve(data)
|
|
2180
|
+
* .map(d => transform(d))
|
|
2181
|
+
* .flatMap(t => FuturableTask.of(() => save(t)));
|
|
2182
|
+
* ```
|
|
2183
|
+
*
|
|
2184
|
+
* @example
|
|
2185
|
+
* ```typescript
|
|
2186
|
+
* // Default values
|
|
2187
|
+
* const task = condition
|
|
2188
|
+
* ? FuturableTask.of(() => fetchData())
|
|
2189
|
+
* : FuturableTask.resolve(defaultData);
|
|
2190
|
+
* ```
|
|
2191
|
+
*/
|
|
2192
|
+
static resolve<U = any>(v: U, signal?: AbortSignal): FuturableTask<U>;
|
|
2193
|
+
/**
|
|
2194
|
+
* Creates a FuturableTask that immediately rejects with the given reason.
|
|
2195
|
+
*
|
|
2196
|
+
* Useful for creating tasks that represent known failures,
|
|
2197
|
+
* or for testing error handling logic.
|
|
2198
|
+
*
|
|
2199
|
+
* @param reason - The rejection reason (typically an Error)
|
|
2200
|
+
* @param signal - Optional AbortSignal for the task
|
|
2201
|
+
* @returns A FuturableTask that rejects with the reason
|
|
2202
|
+
*
|
|
2203
|
+
* @example
|
|
2204
|
+
* ```typescript
|
|
2205
|
+
* const task = FuturableTask.reject(new Error('Not implemented'))
|
|
2206
|
+
* .orElse(() => FuturableTask.of(() => fallbackImplementation()));
|
|
2207
|
+
* ```
|
|
2208
|
+
*
|
|
2209
|
+
* @example
|
|
2210
|
+
* ```typescript
|
|
2211
|
+
* // Conditional errors
|
|
2212
|
+
* const task = value < 0
|
|
2213
|
+
* ? FuturableTask.reject(new Error('Value must be positive'))
|
|
2214
|
+
* : FuturableTask.of(() => process(value));
|
|
2215
|
+
* ```
|
|
2216
|
+
*
|
|
2217
|
+
* @example
|
|
2218
|
+
* ```typescript
|
|
2219
|
+
* // Testing error handlers
|
|
2220
|
+
* const testTask = FuturableTask.reject(new Error('Test error'))
|
|
2221
|
+
* .tapError(err => assert(err.message === 'Test error'));
|
|
2222
|
+
* ```
|
|
2223
|
+
*/
|
|
2224
|
+
static reject<U = never>(reason: any, signal?: AbortSignal): FuturableTask<U>;
|
|
2225
|
+
/**
|
|
2226
|
+
* Executes all tasks in parallel and resolves when all complete.
|
|
2227
|
+
*
|
|
2228
|
+
* All tasks run concurrently. If any task fails, the combined task
|
|
2229
|
+
* fails immediately and all running tasks are cancelled.
|
|
2230
|
+
*
|
|
2231
|
+
* Returns an array of results in the same order as the input tasks.
|
|
2232
|
+
*
|
|
2233
|
+
* @template T - Type of values the tasks produce
|
|
2234
|
+
* @param tasks - Array of FuturableTasks to execute in parallel
|
|
2235
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2236
|
+
* @returns A FuturableTask that resolves with an array of all results
|
|
2237
|
+
*
|
|
2238
|
+
* @example
|
|
2239
|
+
* ```typescript
|
|
2240
|
+
* const tasks = [
|
|
2241
|
+
* FuturableTask.of(() => fetch('/api/users')),
|
|
2242
|
+
* FuturableTask.of(() => fetch('/api/posts')),
|
|
2243
|
+
* FuturableTask.of(() => fetch('/api/comments'))
|
|
2244
|
+
* ];
|
|
2245
|
+
*
|
|
2246
|
+
* const [users, posts, comments] = await FuturableTask.all(tasks).run();
|
|
2247
|
+
* ```
|
|
2248
|
+
*
|
|
2249
|
+
* @example
|
|
2250
|
+
* ```typescript
|
|
2251
|
+
* // With error handling
|
|
2252
|
+
* try {
|
|
2253
|
+
* const results = await FuturableTask.all([task1, task2, task3]).run();
|
|
2254
|
+
* } catch (err) {
|
|
2255
|
+
* // One of the tasks failed, others were cancelled
|
|
2256
|
+
* }
|
|
2257
|
+
* ```
|
|
2258
|
+
*
|
|
2259
|
+
* @example
|
|
2260
|
+
* ```typescript
|
|
2261
|
+
* // Dynamic task lists
|
|
2262
|
+
* const userIds = [1, 2, 3, 4, 5];
|
|
2263
|
+
* const tasks = userIds.map(id =>
|
|
2264
|
+
* FuturableTask.of(() => fetchUser(id))
|
|
2265
|
+
* );
|
|
2266
|
+
* const users = await FuturableTask.all(tasks).run();
|
|
2267
|
+
* ```
|
|
2268
|
+
*/
|
|
2269
|
+
static all<T>(tasks: FuturableTask<T>[], signal?: AbortSignal): FuturableTask<T[]>;
|
|
2270
|
+
/**
|
|
2271
|
+
* Executes all tasks in parallel and waits for all to settle (resolve or reject).
|
|
2272
|
+
*
|
|
2273
|
+
* Never rejects. Returns an array of result objects indicating the outcome
|
|
2274
|
+
* of each task. Each result has a status ('fulfilled' or 'rejected') and
|
|
2275
|
+
* either a value or reason.
|
|
2276
|
+
*
|
|
2277
|
+
* Useful when you want to attempt multiple operations and handle failures individually.
|
|
2278
|
+
*
|
|
2279
|
+
* @template T - Type of values the tasks produce
|
|
2280
|
+
* @param tasks - Array of FuturableTasks to execute
|
|
2281
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2282
|
+
* @returns A FuturableTask that resolves with an array of PromiseSettledResult objects
|
|
2283
|
+
*
|
|
2284
|
+
* @example
|
|
2285
|
+
* ```typescript
|
|
2286
|
+
* const results = await FuturableTask.allSettled([
|
|
2287
|
+
* FuturableTask.resolve(1),
|
|
2288
|
+
* FuturableTask.reject('error'),
|
|
2289
|
+
* FuturableTask.resolve(3)
|
|
2290
|
+
* ]).run();
|
|
2291
|
+
*
|
|
2292
|
+
* results.forEach(result => {
|
|
2293
|
+
* if (result.status === 'fulfilled') {
|
|
2294
|
+
* console.log('Success:', result.value);
|
|
2295
|
+
* } else {
|
|
2296
|
+
* console.log('Failed:', result.reason);
|
|
2297
|
+
* }
|
|
2298
|
+
* });
|
|
2299
|
+
* // Success: 1
|
|
2300
|
+
* // Failed: error
|
|
2301
|
+
* // Success: 3
|
|
2302
|
+
* ```
|
|
2303
|
+
*
|
|
2304
|
+
* @example
|
|
2305
|
+
* ```typescript
|
|
2306
|
+
* // Batch operations with individual error handling
|
|
2307
|
+
* const results = await FuturableTask.allSettled(
|
|
2308
|
+
* userIds.map(id => FuturableTask.of(() => deleteUser(id)))
|
|
2309
|
+
* ).run();
|
|
2310
|
+
*
|
|
2311
|
+
* const succeeded = results.filter(r => r.status === 'fulfilled').length;
|
|
2312
|
+
* const failed = results.filter(r => r.status === 'rejected').length;
|
|
2313
|
+
* console.log(`Deleted ${succeeded}, failed ${failed}`);
|
|
2314
|
+
* ```
|
|
2315
|
+
*/
|
|
2316
|
+
static allSettled<T>(tasks: FuturableTask<T>[], signal?: AbortSignal): FuturableTask<PromiseSettledResult<T>[]>;
|
|
2317
|
+
/**
|
|
2318
|
+
* Executes all tasks in parallel and resolves/rejects with the first to settle.
|
|
2319
|
+
*
|
|
2320
|
+
* Once one task settles (successfully or with error), all other tasks are cancelled.
|
|
2321
|
+
* The result matches the first settled task - success or failure.
|
|
2322
|
+
*
|
|
2323
|
+
* Useful for timeout patterns or racing multiple data sources.
|
|
2324
|
+
*
|
|
2325
|
+
* @template T - Type of values the tasks produce
|
|
2326
|
+
* @param tasks - Array of FuturableTasks to race
|
|
2327
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2328
|
+
* @returns A FuturableTask that settles with the first task's result
|
|
2329
|
+
*
|
|
2330
|
+
* @example
|
|
2331
|
+
* ```typescript
|
|
2332
|
+
* const fastest = await FuturableTask.race([
|
|
2333
|
+
* FuturableTask.of(() => fetchFromCDN1()),
|
|
2334
|
+
* FuturableTask.of(() => fetchFromCDN2()),
|
|
2335
|
+
* FuturableTask.of(() => fetchFromCDN3())
|
|
2336
|
+
* ]).run();
|
|
2337
|
+
*
|
|
2338
|
+
* console.log('Fastest CDN responded with:', fastest);
|
|
2339
|
+
* ```
|
|
2340
|
+
*
|
|
2341
|
+
* @example
|
|
2342
|
+
* ```typescript
|
|
2343
|
+
* // Timeout pattern
|
|
2344
|
+
* const result = await FuturableTask.race([
|
|
2345
|
+
* FuturableTask.of(() => slowOperation()),
|
|
2346
|
+
* FuturableTask.delay(5000).flatMap(() =>
|
|
2347
|
+
* FuturableTask.reject(new Error('Timeout'))
|
|
2348
|
+
* )
|
|
2349
|
+
* ]).run();
|
|
2350
|
+
* ```
|
|
2351
|
+
*
|
|
2352
|
+
* @example
|
|
2353
|
+
* ```typescript
|
|
2354
|
+
* // First successful response wins
|
|
2355
|
+
* const data = await FuturableTask.race([
|
|
2356
|
+
* cacheTask,
|
|
2357
|
+
* apiTask
|
|
2358
|
+
* ]).run(); // Uses cache if available, API otherwise
|
|
2359
|
+
* ```
|
|
2360
|
+
*/
|
|
2361
|
+
static race<T>(tasks: FuturableTask<T>[], signal?: AbortSignal): FuturableTask<T>;
|
|
2362
|
+
/**
|
|
2363
|
+
* Executes all tasks in parallel and resolves with the first successful result.
|
|
2364
|
+
*
|
|
2365
|
+
* Ignores failures and only resolves when at least one task succeeds.
|
|
2366
|
+
* Only rejects if ALL tasks fail (with an AggregateError containing all errors).
|
|
2367
|
+
*
|
|
2368
|
+
* Perfect for fallback scenarios with multiple alternatives.
|
|
2369
|
+
*
|
|
2370
|
+
* @template T - Type of values the tasks produce
|
|
2371
|
+
* @param tasks - Array of FuturableTasks to execute
|
|
2372
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2373
|
+
* @returns A FuturableTask that resolves with the first success
|
|
2374
|
+
*
|
|
2375
|
+
* @example
|
|
2376
|
+
* ```typescript
|
|
2377
|
+
* const data = await FuturableTask.any([
|
|
2378
|
+
* FuturableTask.of(() => fetchFromPrimaryServer()),
|
|
2379
|
+
* FuturableTask.of(() => fetchFromBackupServer1()),
|
|
2380
|
+
* FuturableTask.of(() => fetchFromBackupServer2())
|
|
2381
|
+
* ]).run();
|
|
2382
|
+
*
|
|
2383
|
+
* // Returns data from whichever server responds successfully first
|
|
2384
|
+
* ```
|
|
2385
|
+
*
|
|
2386
|
+
* @example
|
|
2387
|
+
* ```typescript
|
|
2388
|
+
* // Multiple fallback sources
|
|
2389
|
+
* const config = await FuturableTask.any([
|
|
2390
|
+
* FuturableTask.of(() => loadFromEnv()),
|
|
2391
|
+
* FuturableTask.of(() => loadFromFile()),
|
|
2392
|
+
* FuturableTask.of(() => loadDefaults())
|
|
2393
|
+
* ]).run();
|
|
2394
|
+
* ```
|
|
2395
|
+
*
|
|
2396
|
+
* @example
|
|
2397
|
+
* ```typescript
|
|
2398
|
+
* // All fail case
|
|
2399
|
+
* try {
|
|
2400
|
+
* await FuturableTask.any([
|
|
2401
|
+
* FuturableTask.reject('error1'),
|
|
2402
|
+
* FuturableTask.reject('error2')
|
|
2403
|
+
* ]).run();
|
|
2404
|
+
* } catch (err) {
|
|
2405
|
+
* console.log(err.errors); // ['error1', 'error2']
|
|
2406
|
+
* }
|
|
2407
|
+
* ```
|
|
2408
|
+
*/
|
|
2409
|
+
static any<T>(tasks: FuturableTask<T>[], signal?: AbortSignal): FuturableTask<T>;
|
|
2410
|
+
/**
|
|
2411
|
+
* Creates a FuturableTask that resolves after a specified delay.
|
|
2412
|
+
*
|
|
2413
|
+
* The delay is lazy - it only starts when run() is called.
|
|
2414
|
+
* The task resolves with void (no value).
|
|
2415
|
+
*
|
|
2416
|
+
* Useful for adding delays in task chains or implementing backoff strategies.
|
|
2417
|
+
*
|
|
2418
|
+
* @param ms - Delay duration in milliseconds
|
|
2419
|
+
* @param signal - Optional AbortSignal for the task
|
|
2420
|
+
* @returns A FuturableTask that resolves after the delay
|
|
2421
|
+
*
|
|
2422
|
+
* @example
|
|
2423
|
+
* ```typescript
|
|
2424
|
+
* await FuturableTask.delay(2000).run(); // Waits 2 seconds
|
|
2425
|
+
* ```
|
|
2426
|
+
*
|
|
2427
|
+
* @example
|
|
2428
|
+
* ```typescript
|
|
2429
|
+
* // Chaining with delays
|
|
2430
|
+
* await FuturableTask.resolve('Starting...')
|
|
2431
|
+
* .tap(console.log)
|
|
2432
|
+
* .andThen(FuturableTask.delay(1000))
|
|
2433
|
+
* .andThen(FuturableTask.resolve('Done!'))
|
|
2434
|
+
* .tap(console.log)
|
|
2435
|
+
* .run();
|
|
2436
|
+
* ```
|
|
2437
|
+
*
|
|
2438
|
+
* @example
|
|
2439
|
+
* ```typescript
|
|
2440
|
+
* // Cancellable delay
|
|
2441
|
+
* const delayTask = FuturableTask.delay(5000);
|
|
2442
|
+
* delayTask.run();
|
|
2443
|
+
* delayTask.cancel(); // Cancels the delay
|
|
2444
|
+
* ```
|
|
2445
|
+
*/
|
|
2446
|
+
static delay(ms: number, signal?: AbortSignal): FuturableTask<void>;
|
|
2447
|
+
/**
|
|
2448
|
+
* Creates a FuturableTask from an event listener on a given target.
|
|
2449
|
+
*
|
|
2450
|
+
* The listener is only attached when run() is called, and automatically
|
|
2451
|
+
* unsubscribes after the first occurrence to maintain single-value semantics.
|
|
2452
|
+
*
|
|
2453
|
+
* If the task is cancelled before the event fires, the listener is properly
|
|
2454
|
+
* removed to prevent memory leaks.
|
|
2455
|
+
*
|
|
2456
|
+
* @template E - The type of the Event object
|
|
2457
|
+
* @param target - The DOM element, Window, or event target
|
|
2458
|
+
* @param name - The name of the event to listen for (e.g., 'click', 'message')
|
|
2459
|
+
* @param opts - Standard listener options like capture, passive, or once
|
|
2460
|
+
* @param signal - Optional AbortSignal for the task
|
|
2461
|
+
* @returns A new FuturableTask that resolves with the Event object
|
|
2462
|
+
*
|
|
2463
|
+
* @example
|
|
2464
|
+
* ```typescript
|
|
2465
|
+
* // Listen for a one-time click
|
|
2466
|
+
* const clickTask = FuturableTask.fromEvent(
|
|
2467
|
+
* document.getElementById('myBtn'),
|
|
2468
|
+
* 'click',
|
|
2469
|
+
* { passive: true }
|
|
2470
|
+
* );
|
|
2471
|
+
*
|
|
2472
|
+
* const event = await clickTask.run();
|
|
2473
|
+
* console.log('Button clicked at:', event.timeStamp);
|
|
2474
|
+
* ```
|
|
2475
|
+
*
|
|
2476
|
+
* @example
|
|
2477
|
+
* ```typescript
|
|
2478
|
+
* // With timeout
|
|
2479
|
+
* try {
|
|
2480
|
+
* const event = await FuturableTask
|
|
2481
|
+
* .fromEvent(button, 'click')
|
|
2482
|
+
* .timeout(5000, new Error('No click within 5 seconds'))
|
|
2483
|
+
* .run();
|
|
2484
|
+
* } catch (err) {
|
|
2485
|
+
* console.log('Timed out waiting for click');
|
|
2486
|
+
* }
|
|
2487
|
+
* ```
|
|
2488
|
+
*
|
|
2489
|
+
* @example
|
|
2490
|
+
* ```typescript
|
|
2491
|
+
* // Cancellable event listener
|
|
2492
|
+
* const task = FuturableTask.fromEvent(window, 'resize');
|
|
2493
|
+
* task.run();
|
|
2494
|
+
* // ... later
|
|
2495
|
+
* task.cancel(); // Removes the listener
|
|
2496
|
+
* ```
|
|
2497
|
+
*/
|
|
2498
|
+
static fromEvent<E extends Event>(target: EventTarget, name: string, opts?: AddEventListenerOptions, signal?: AbortSignal): FuturableTask<E>;
|
|
2499
|
+
/**
|
|
2500
|
+
* Executes an array of tasks sequentially, one after another.
|
|
2501
|
+
*
|
|
2502
|
+
* Each task waits for the previous one to complete before starting.
|
|
2503
|
+
* If any task fails, the sequence stops and the error is propagated.
|
|
2504
|
+
*
|
|
2505
|
+
* Returns an array of all results in order.
|
|
2506
|
+
*
|
|
2507
|
+
* @template T - Type of values the tasks produce
|
|
2508
|
+
* @param tasks - Array of FuturableTasks to execute in sequence
|
|
2509
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2510
|
+
* @returns A FuturableTask that resolves with an array of all results
|
|
2511
|
+
*
|
|
2512
|
+
* @example
|
|
2513
|
+
* ```typescript
|
|
2514
|
+
* const tasks = [
|
|
2515
|
+
* FuturableTask.of(() => step1()),
|
|
2516
|
+
* FuturableTask.of(() => step2()),
|
|
2517
|
+
* FuturableTask.of(() => step3())
|
|
2518
|
+
* ];
|
|
2519
|
+
*
|
|
2520
|
+
* const results = await FuturableTask.sequence(tasks).run();
|
|
2521
|
+
* // step1 completes, then step2, then step3
|
|
2522
|
+
* ```
|
|
2523
|
+
*
|
|
2524
|
+
* @example
|
|
2525
|
+
* ```typescript
|
|
2526
|
+
* // Database migrations in order
|
|
2527
|
+
* const migrations = [
|
|
2528
|
+
* FuturableTask.of(() => createUsersTable()),
|
|
2529
|
+
* FuturableTask.of(() => createPostsTable()),
|
|
2530
|
+
* FuturableTask.of(() => addForeignKeys())
|
|
2531
|
+
* ];
|
|
2532
|
+
* await FuturableTask.sequence(migrations).run();
|
|
2533
|
+
* ```
|
|
2534
|
+
*
|
|
2535
|
+
* @example
|
|
2536
|
+
* ```typescript
|
|
2537
|
+
* // With delays between steps
|
|
2538
|
+
* const steps = [
|
|
2539
|
+
* FuturableTask.of(() => init()),
|
|
2540
|
+
* FuturableTask.delay(1000).andThen(FuturableTask.of(() => warmup())),
|
|
2541
|
+
* FuturableTask.delay(2000).andThen(FuturableTask.of(() => start()))
|
|
2542
|
+
* ];
|
|
2543
|
+
* await FuturableTask.sequence(steps).run();
|
|
2544
|
+
* ```
|
|
2545
|
+
*/
|
|
2546
|
+
static sequence<T>(tasks: FuturableTask<T>[], signal?: AbortSignal): FuturableTask<T[]>;
|
|
2547
|
+
/**
|
|
2548
|
+
* Executes tasks in parallel with a concurrency limit.
|
|
2549
|
+
*
|
|
2550
|
+
* Limits the number of tasks running simultaneously. When a task completes,
|
|
2551
|
+
* the next queued task starts. If any task fails, all running tasks are
|
|
2552
|
+
* cancelled and the error is propagated.
|
|
2553
|
+
*
|
|
2554
|
+
* Returns results in the same order as input tasks.
|
|
2555
|
+
*
|
|
2556
|
+
* @template T - Type of value the tasks produce
|
|
2557
|
+
* @param tasks - Array of FuturableTasks
|
|
2558
|
+
* @param limit - Maximum number of concurrent executions (default: 5)
|
|
2559
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2560
|
+
* @returns A FuturableTask that resolves with an array of all results
|
|
2561
|
+
*
|
|
2562
|
+
* @example
|
|
2563
|
+
* ```typescript
|
|
2564
|
+
* // Process 100 items with max 5 concurrent operations
|
|
2565
|
+
* const tasks = items.map(item =>
|
|
2566
|
+
* FuturableTask.of(() => processItem(item))
|
|
2567
|
+
* );
|
|
2568
|
+
*
|
|
2569
|
+
* const results = await FuturableTask.parallel(tasks, 5).run();
|
|
2570
|
+
* ```
|
|
2571
|
+
*
|
|
2572
|
+
* @example
|
|
2573
|
+
* ```typescript
|
|
2574
|
+
* // API rate limiting - max 3 concurrent requests
|
|
2575
|
+
* const userTasks = userIds.map(id =>
|
|
2576
|
+
* FuturableTask.of(() => fetch(`/api/users/${id}`))
|
|
2577
|
+
* );
|
|
2578
|
+
*
|
|
2579
|
+
* const users = await FuturableTask.parallel(userTasks, 3).run();
|
|
2580
|
+
* ```
|
|
2581
|
+
*
|
|
2582
|
+
* @example
|
|
2583
|
+
* ```typescript
|
|
2584
|
+
* // With error handling
|
|
2585
|
+
* try {
|
|
2586
|
+
* const results = await FuturableTask.parallel(tasks, 10).run();
|
|
2587
|
+
* } catch (err) {
|
|
2588
|
+
* // One task failed, all running tasks were cancelled
|
|
2589
|
+
* }
|
|
2590
|
+
* ```
|
|
2591
|
+
*/
|
|
2592
|
+
static parallel<T>(tasks: FuturableTask<T>[], limit?: number, signal?: AbortSignal): FuturableTask<T[]>;
|
|
2593
|
+
/**
|
|
2594
|
+
* Creates a higher-order function (limiter) to control the maximum number
|
|
2595
|
+
* of concurrent executions for a set of Tasks.
|
|
2596
|
+
*
|
|
2597
|
+
* Tasks do not enter the queue or increment the running counter until
|
|
2598
|
+
* their .run() method is explicitly called. Tasks are processed FIFO.
|
|
2599
|
+
*
|
|
2600
|
+
* Optional event hooks allow tracking active tasks, completions, errors, and idle states.
|
|
2601
|
+
*
|
|
2602
|
+
* The returned limiter function wraps tasks and includes readonly properties
|
|
2603
|
+
* for monitoring (activeCount, pendingCount, concurrency).
|
|
2604
|
+
*
|
|
2605
|
+
* @param concurrency - Maximum number of tasks allowed to run simultaneously (must be >= 1)
|
|
2606
|
+
* @param events - Optional lifecycle hooks for monitoring
|
|
2607
|
+
* @param signal - Optional AbortSignal for the limiter
|
|
2608
|
+
* @returns A decorator function that limits task concurrency
|
|
2609
|
+
*
|
|
2610
|
+
* @example
|
|
2611
|
+
* ```typescript
|
|
2612
|
+
* const limiter = FuturableTask.createLimiter(2, {
|
|
2613
|
+
* onActive: (task) => console.log('Task started'),
|
|
2614
|
+
* onCompleted: (result) => console.log('Task completed:', result),
|
|
2615
|
+
* onIdle: () => console.log('All tasks finished!')
|
|
2616
|
+
* });
|
|
2617
|
+
*
|
|
2618
|
+
* // Wrap your original tasks
|
|
2619
|
+
* const tasks = [1,2,3,4,5].map(i => limiter(
|
|
2620
|
+
* FuturableTask.of(async () => {
|
|
2621
|
+
* await delay(1000);
|
|
2622
|
+
* return i;
|
|
2623
|
+
* })
|
|
2624
|
+
* ));
|
|
2625
|
+
*
|
|
2626
|
+
* // Execution starts here, respects limit of 2
|
|
2627
|
+
* const results = await FuturableTask.all(tasks).run();
|
|
2628
|
+
* console.log(results); // [1, 2, 3, 4, 5]
|
|
2629
|
+
* ```
|
|
2630
|
+
*
|
|
2631
|
+
* @example
|
|
2632
|
+
* ```typescript
|
|
2633
|
+
* // Monitoring limiter state
|
|
2634
|
+
* const limiter = FuturableTask.createLimiter(3);
|
|
2635
|
+
*
|
|
2636
|
+
* console.log(limiter.concurrency); // 3
|
|
2637
|
+
* console.log(limiter.activeCount); // 0
|
|
2638
|
+
* console.log(limiter.pendingCount); // 0
|
|
2639
|
+
*
|
|
2640
|
+
* const task = limiter(FuturableTask.of(() => operation()));
|
|
2641
|
+
* task.run();
|
|
2642
|
+
*
|
|
2643
|
+
* console.log(limiter.activeCount); // 1
|
|
2644
|
+
* ```
|
|
2645
|
+
*
|
|
2646
|
+
* @example
|
|
2647
|
+
* ```typescript
|
|
2648
|
+
* // API rate limiting
|
|
2649
|
+
* const apiLimiter = FuturableTask.createLimiter(5, {
|
|
2650
|
+
* onError: (err) => logger.error('API call failed', err)
|
|
2651
|
+
* });
|
|
2652
|
+
*
|
|
2653
|
+
* const fetchUser = (id) => apiLimiter(
|
|
2654
|
+
* FuturableTask.of(() => fetch(`/api/users/${id}`))
|
|
2655
|
+
* );
|
|
2656
|
+
*
|
|
2657
|
+
* const users = await FuturableTask.all(
|
|
2658
|
+
* userIds.map(fetchUser)
|
|
2659
|
+
* ).run();
|
|
2660
|
+
* ```
|
|
2661
|
+
*/
|
|
2662
|
+
static createLimiter(concurrency: number, events?: LimiterEvents, signal?: AbortSignal): FuturableTaskLimiter;
|
|
2663
|
+
/**
|
|
2664
|
+
* Composes a FuturableTask through a sequence of transformation operators.
|
|
2665
|
+
*
|
|
2666
|
+
* Applies operators from left to right, similar to pipe but starting with
|
|
2667
|
+
* an initial task. Type-safe composition ensures each operator's output
|
|
2668
|
+
* matches the next operator's input.
|
|
2669
|
+
*
|
|
2670
|
+
* Useful for building reusable task pipelines.
|
|
2671
|
+
*
|
|
2672
|
+
* @template T - The type of the value produced by the initial task
|
|
2673
|
+
* @template R - The type of the value produced by the final task
|
|
2674
|
+
* @param initial - The starting FuturableTask to transform
|
|
2675
|
+
* @param operators - Variadic list of transformation functions
|
|
2676
|
+
* @returns A new FuturableTask representing the composed operations
|
|
2677
|
+
*
|
|
2678
|
+
* @example
|
|
2679
|
+
* ```typescript
|
|
2680
|
+
* // Linear composition
|
|
2681
|
+
* const finalTask = FuturableTask.compose(
|
|
2682
|
+
* FuturableTask.of(() => fetchUser(1)),
|
|
2683
|
+
* (t) => t.retry(3),
|
|
2684
|
+
* (t) => t.timeout(5000),
|
|
2685
|
+
* (t) => t.map(user => user.name)
|
|
2686
|
+
* );
|
|
2687
|
+
*
|
|
2688
|
+
* const name = await finalTask.run();
|
|
2689
|
+
* ```
|
|
2690
|
+
*
|
|
2691
|
+
* @example
|
|
2692
|
+
* ```typescript
|
|
2693
|
+
* // Building reusable pipelines
|
|
2694
|
+
* const robustAPI = <T>(task: FuturableTask<T>) =>
|
|
2695
|
+
* FuturableTask.compose(
|
|
2696
|
+
* task,
|
|
2697
|
+
* t => t.retry(3, 1000),
|
|
2698
|
+
* t => t.timeout(10000),
|
|
2699
|
+
* t => t.tapError(err => logger.error(err))
|
|
2700
|
+
* );
|
|
2701
|
+
*
|
|
2702
|
+
* const userData = await robustAPI(
|
|
2703
|
+
* FuturableTask.of(() => fetch('/api/user'))
|
|
2704
|
+
* ).run();
|
|
2705
|
+
* ```
|
|
2706
|
+
*
|
|
2707
|
+
* @example
|
|
2708
|
+
* ```typescript
|
|
2709
|
+
* // Data processing pipeline
|
|
2710
|
+
* const processData = FuturableTask.compose(
|
|
2711
|
+
* FuturableTask.of(() => loadRawData()),
|
|
2712
|
+
* t => t.map(data => validate(data)),
|
|
2713
|
+
* t => t.map(data => transform(data)),
|
|
2714
|
+
* t => t.flatMap(data => FuturableTask.of(() => save(data)))
|
|
2715
|
+
* );
|
|
2716
|
+
* ```
|
|
2717
|
+
*/
|
|
2718
|
+
static compose<T, R>(initial: FuturableTask<T>, ...operators: Array<(t: FuturableTask<any>) => FuturableTask<any>>): FuturableTask<R>;
|
|
2719
|
+
/**
|
|
2720
|
+
* Filters an array of tasks based on a predicate.
|
|
2721
|
+
*
|
|
2722
|
+
* Executes all tasks sequentially, then applies the predicate to each result.
|
|
2723
|
+
* Only values that pass the predicate are included in the final array.
|
|
2724
|
+
*
|
|
2725
|
+
* The predicate can be async, allowing for asynchronous filtering logic.
|
|
2726
|
+
*
|
|
2727
|
+
* @template T - Type of values the tasks produce
|
|
2728
|
+
* @param tasks - Array of tasks to filter
|
|
2729
|
+
* @param predicate - Function to test each value (can be async)
|
|
2730
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2731
|
+
* @returns A FuturableTask that resolves with filtered values
|
|
2732
|
+
*
|
|
2733
|
+
* @example
|
|
2734
|
+
* ```typescript
|
|
2735
|
+
* const tasks = [
|
|
2736
|
+
* FuturableTask.resolve(1),
|
|
2737
|
+
* FuturableTask.resolve(2),
|
|
2738
|
+
* FuturableTask.resolve(3),
|
|
2739
|
+
* FuturableTask.resolve(4)
|
|
2740
|
+
* ];
|
|
2741
|
+
*
|
|
2742
|
+
* const evenNumbers = await FuturableTask
|
|
2743
|
+
* .filter(tasks, n => n % 2 === 0)
|
|
2744
|
+
* .run(); // [2, 4]
|
|
2745
|
+
* ```
|
|
2746
|
+
*
|
|
2747
|
+
* @example
|
|
2748
|
+
* ```typescript
|
|
2749
|
+
* // Async filtering
|
|
2750
|
+
* const userTasks = ids.map(id => FuturableTask.of(() => fetchUser(id)));
|
|
2751
|
+
*
|
|
2752
|
+
* const activeUsers = await FuturableTask.filter(
|
|
2753
|
+
* userTasks,
|
|
2754
|
+
* async user => await isActive(user.id)
|
|
2755
|
+
* ).run();
|
|
2756
|
+
* ```
|
|
2757
|
+
*
|
|
2758
|
+
* @example
|
|
2759
|
+
* ```typescript
|
|
2760
|
+
* // Filtering with validation
|
|
2761
|
+
* const results = await FuturableTask.filter(
|
|
2762
|
+
* dataTasks,
|
|
2763
|
+
* data => data.isValid && data.score > 0.5
|
|
2764
|
+
* ).run();
|
|
2765
|
+
* ```
|
|
2766
|
+
*/
|
|
2767
|
+
static filter<T>(tasks: FuturableTask<T>[], predicate: (value: T) => boolean | Promise<boolean>, signal?: AbortSignal): FuturableTask<T[]>;
|
|
2768
|
+
/**
|
|
2769
|
+
* Reduces an array of tasks to a single value.
|
|
2770
|
+
*
|
|
2771
|
+
* Executes tasks sequentially, accumulating a result by applying the reducer
|
|
2772
|
+
* function to each task's value. The reducer receives the accumulator, current
|
|
2773
|
+
* value, and index.
|
|
2774
|
+
*
|
|
2775
|
+
* The reducer can be async.
|
|
2776
|
+
*
|
|
2777
|
+
* @template T - Type of values the tasks produce
|
|
2778
|
+
* @template U - Type of the accumulated result
|
|
2779
|
+
* @param tasks - Array of tasks to reduce
|
|
2780
|
+
* @param reducer - Function to accumulate results (can be async)
|
|
2781
|
+
* @param initialValue - Initial value for the accumulator
|
|
2782
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2783
|
+
* @returns A FuturableTask that resolves with the final accumulated value
|
|
2784
|
+
*
|
|
2785
|
+
* @example
|
|
2786
|
+
* ```typescript
|
|
2787
|
+
* const tasks = [
|
|
2788
|
+
* FuturableTask.resolve(1),
|
|
2789
|
+
* FuturableTask.resolve(2),
|
|
2790
|
+
* FuturableTask.resolve(3)
|
|
2791
|
+
* ];
|
|
2792
|
+
*
|
|
2793
|
+
* const sum = await FuturableTask
|
|
2794
|
+
* .reduce(tasks, (acc, n) => acc + n, 0)
|
|
2795
|
+
* .run(); // 6
|
|
2796
|
+
* ```
|
|
2797
|
+
*
|
|
2798
|
+
* @example
|
|
2799
|
+
* ```typescript
|
|
2800
|
+
* // Building an object from tasks
|
|
2801
|
+
* const userTasks = ids.map(id => FuturableTask.of(() => fetchUser(id)));
|
|
2802
|
+
*
|
|
2803
|
+
* const userMap = await FuturableTask.reduce(
|
|
2804
|
+
* userTasks,
|
|
2805
|
+
* (map, user) => ({ ...map, [user.id]: user }),
|
|
2806
|
+
* {}
|
|
2807
|
+
* ).run();
|
|
2808
|
+
* ```
|
|
2809
|
+
*
|
|
2810
|
+
* @example
|
|
2811
|
+
* ```typescript
|
|
2812
|
+
* // Async reducer
|
|
2813
|
+
* const total = await FuturableTask.reduce(
|
|
2814
|
+
* tasks,
|
|
2815
|
+
* async (acc, value) => {
|
|
2816
|
+
* const processed = await processValue(value);
|
|
2817
|
+
* return acc + processed;
|
|
2818
|
+
* },
|
|
2819
|
+
* 0
|
|
2820
|
+
* ).run();
|
|
2821
|
+
* ```
|
|
2822
|
+
*/
|
|
2823
|
+
static reduce<T, U>(tasks: FuturableTask<T>[], reducer: (acc: U, value: T, index: number) => U | Promise<U>, initialValue: U, signal?: AbortSignal): FuturableTask<U>;
|
|
2824
|
+
/**
|
|
2825
|
+
* Repeatedly executes a task while a condition is true.
|
|
2826
|
+
*
|
|
2827
|
+
* Evaluates the condition before each execution. Stops when the condition
|
|
2828
|
+
* returns false or the task fails. Returns an array of all results.
|
|
2829
|
+
*
|
|
2830
|
+
* The condition can be async.
|
|
2831
|
+
*
|
|
2832
|
+
* @template T - Type of value the task produces
|
|
2833
|
+
* @param condition - Predicate evaluated before each execution (can be async)
|
|
2834
|
+
* @param task - The task to execute repeatedly
|
|
2835
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2836
|
+
* @returns A FuturableTask that resolves with an array of all results
|
|
2837
|
+
*
|
|
2838
|
+
* @example
|
|
2839
|
+
* ```typescript
|
|
2840
|
+
* let counter = 0;
|
|
2841
|
+
* const incrementTask = FuturableTask.of(() => ++counter);
|
|
2842
|
+
*
|
|
2843
|
+
* const results = await FuturableTask
|
|
2844
|
+
* .whilst(() => counter < 5, incrementTask)
|
|
2845
|
+
* .run(); // [1, 2, 3, 4, 5]
|
|
2846
|
+
* ```
|
|
2847
|
+
*
|
|
2848
|
+
* @example
|
|
2849
|
+
* ```typescript
|
|
2850
|
+
* // Async condition
|
|
2851
|
+
* const results = await FuturableTask.whilst(
|
|
2852
|
+
* async () => await hasMoreData(),
|
|
2853
|
+
* FuturableTask.of(() => fetchNextBatch())
|
|
2854
|
+
* ).run();
|
|
2855
|
+
* ```
|
|
2856
|
+
*
|
|
2857
|
+
* @example
|
|
2858
|
+
* ```typescript
|
|
2859
|
+
* // Paginated fetching
|
|
2860
|
+
* let page = 1;
|
|
2861
|
+
* let hasMore = true;
|
|
2862
|
+
*
|
|
2863
|
+
* const allData = await FuturableTask.whilst(
|
|
2864
|
+
* () => hasMore,
|
|
2865
|
+
* FuturableTask.of(async () => {
|
|
2866
|
+
* const response = await fetchPage(page++);
|
|
2867
|
+
* hasMore = response.hasMore;
|
|
2868
|
+
* return response.data;
|
|
2869
|
+
* })
|
|
2870
|
+
* ).run();
|
|
2871
|
+
* ```
|
|
2872
|
+
*/
|
|
2873
|
+
static whilst<T>(condition: () => boolean | Promise<boolean>, task: FuturableTask<T>, signal?: AbortSignal): FuturableTask<T[]>;
|
|
2874
|
+
/**
|
|
2875
|
+
* Repeatedly executes a task until a condition becomes true.
|
|
2876
|
+
*
|
|
2877
|
+
* Opposite of whilst - executes while the condition is false.
|
|
2878
|
+
* Evaluates the condition before each execution.
|
|
2879
|
+
*
|
|
2880
|
+
* The condition can be async.
|
|
2881
|
+
*
|
|
2882
|
+
* @template T - Type of value the task produces
|
|
2883
|
+
* @param condition - Predicate that stops execution when true (can be async)
|
|
2884
|
+
* @param task - The task to execute repeatedly
|
|
2885
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2886
|
+
* @returns A FuturableTask that resolves with an array of all results
|
|
2887
|
+
*
|
|
2888
|
+
* @example
|
|
2889
|
+
* ```typescript
|
|
2890
|
+
* let counter = 0;
|
|
2891
|
+
* const incrementTask = FuturableTask.of(() => ++counter);
|
|
2892
|
+
*
|
|
2893
|
+
* const results = await FuturableTask
|
|
2894
|
+
* .until(() => counter >= 5, incrementTask)
|
|
2895
|
+
* .run(); // [1, 2, 3, 4, 5]
|
|
2896
|
+
* ```
|
|
2897
|
+
*
|
|
2898
|
+
* @example
|
|
2899
|
+
* ```typescript
|
|
2900
|
+
* // Polling until success
|
|
2901
|
+
* const results = await FuturableTask.until(
|
|
2902
|
+
* async () => await isReady(),
|
|
2903
|
+
* FuturableTask.of(() => checkStatus()).delay(1000)
|
|
2904
|
+
* ).run();
|
|
2905
|
+
* ```
|
|
2906
|
+
*
|
|
2907
|
+
* @example
|
|
2908
|
+
* ```typescript
|
|
2909
|
+
* // Retry until success or limit reached
|
|
2910
|
+
* let attempts = 0;
|
|
2911
|
+
* const MAX_ATTEMPTS = 10;
|
|
2912
|
+
*
|
|
2913
|
+
* await FuturableTask.until(
|
|
2914
|
+
* () => attempts >= MAX_ATTEMPTS,
|
|
2915
|
+
* FuturableTask.of(async () => {
|
|
2916
|
+
* attempts++;
|
|
2917
|
+
* return await tryOperation();
|
|
2918
|
+
* }).orElse(() => FuturableTask.resolve(null))
|
|
2919
|
+
* ).run();
|
|
2920
|
+
* ```
|
|
2921
|
+
*/
|
|
2922
|
+
static until<T>(condition: () => boolean | Promise<boolean>, task: FuturableTask<T>, signal?: AbortSignal): FuturableTask<T[]>;
|
|
2923
|
+
/**
|
|
2924
|
+
* Executes a task factory n times, collecting all results.
|
|
2925
|
+
*
|
|
2926
|
+
* The factory receives the current index (0 to n-1) and returns a task.
|
|
2927
|
+
* Executes sequentially. If any execution fails, the entire operation fails.
|
|
2928
|
+
*
|
|
2929
|
+
* @template T - Type of value the tasks produce
|
|
2930
|
+
* @param n - Number of times to execute (must be >= 0)
|
|
2931
|
+
* @param taskFactory - Function that creates a task for each iteration
|
|
2932
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2933
|
+
* @returns A FuturableTask that resolves with an array of all results
|
|
2934
|
+
*
|
|
2935
|
+
* @example
|
|
2936
|
+
* ```typescript
|
|
2937
|
+
* const results = await FuturableTask
|
|
2938
|
+
* .times(5, i => FuturableTask.resolve(i * 2))
|
|
2939
|
+
* .run(); // [0, 2, 4, 6, 8]
|
|
2940
|
+
* ```
|
|
2941
|
+
*
|
|
2942
|
+
* @example
|
|
2943
|
+
* ```typescript
|
|
2944
|
+
* // Fetch multiple pages
|
|
2945
|
+
* const pages = await FuturableTask.times(
|
|
2946
|
+
* 10,
|
|
2947
|
+
* i => FuturableTask.of(() => fetch(`/api/data?page=${i}`))
|
|
2948
|
+
* ).run();
|
|
2949
|
+
* ```
|
|
2950
|
+
*
|
|
2951
|
+
* @example
|
|
2952
|
+
* ```typescript
|
|
2953
|
+
* // Create test data
|
|
2954
|
+
* const users = await FuturableTask.times(
|
|
2955
|
+
* 100,
|
|
2956
|
+
* i => FuturableTask.of(() => createUser({
|
|
2957
|
+
* name: `User ${i}`,
|
|
2958
|
+
* email: `user${i}@example.com`
|
|
2959
|
+
* }))
|
|
2960
|
+
* ).run();
|
|
2961
|
+
* ```
|
|
2962
|
+
*/
|
|
2963
|
+
static times<T>(n: number, taskFactory: (index: number) => FuturableTask<T>, signal?: AbortSignal): FuturableTask<T[]>;
|
|
2964
|
+
/**
|
|
2965
|
+
* Maps an array of values to tasks and executes them in sequence.
|
|
2966
|
+
*
|
|
2967
|
+
* Combines map and sequence - applies a function to each value to create a task,
|
|
2968
|
+
* then executes all tasks sequentially.
|
|
2969
|
+
*
|
|
2970
|
+
* The mapping function receives both the value and its index.
|
|
2971
|
+
*
|
|
2972
|
+
* @template T - Type of input values
|
|
2973
|
+
* @template U - Type of values the tasks produce
|
|
2974
|
+
* @param values - Array of values to map
|
|
2975
|
+
* @param fn - Function that maps each value to a task
|
|
2976
|
+
* @param signal - Optional AbortSignal for the combined task
|
|
2977
|
+
* @returns A FuturableTask that resolves with an array of all results
|
|
2978
|
+
*
|
|
2979
|
+
* @example
|
|
2980
|
+
* ```typescript
|
|
2981
|
+
* const userIds = [1, 2, 3, 4, 5];
|
|
2982
|
+
* const users = await FuturableTask
|
|
2983
|
+
* .traverse(userIds, id => FuturableTask.of(() => fetchUser(id)))
|
|
2984
|
+
* .run();
|
|
2985
|
+
* ```
|
|
2986
|
+
*
|
|
2987
|
+
* @example
|
|
2988
|
+
* ```typescript
|
|
2989
|
+
* // With index
|
|
2990
|
+
* const results = await FuturableTask.traverse(
|
|
2991
|
+
* ['a', 'b', 'c'],
|
|
2992
|
+
* (letter, index) => FuturableTask.of(() => `${index}: ${letter}`)
|
|
2993
|
+
* ).run(); // ['0: a', '1: b', '2: c']
|
|
2994
|
+
* ```
|
|
2995
|
+
*
|
|
2996
|
+
* @example
|
|
2997
|
+
* ```typescript
|
|
2998
|
+
* // Processing files sequentially
|
|
2999
|
+
* const processed = await FuturableTask.traverse(
|
|
3000
|
+
* filePaths,
|
|
3001
|
+
* path => FuturableTask.of(() => processFile(path))
|
|
3002
|
+
* ).run();
|
|
3003
|
+
* ```
|
|
3004
|
+
*/
|
|
3005
|
+
static traverse<T, U>(values: T[], fn: (value: T, index: number) => FuturableTask<U>, signal?: AbortSignal): FuturableTask<U[]>;
|
|
3006
|
+
/**
|
|
3007
|
+
* Static method to create a fetch task directly.
|
|
3008
|
+
*
|
|
3009
|
+
* @param url - The URL to fetch
|
|
3010
|
+
* @param opts - Optional Fetch API options
|
|
3011
|
+
* @param signal - Optional AbortSignal for the task
|
|
3012
|
+
* @returns A FuturableTask that resolves with the Response
|
|
3013
|
+
*
|
|
3014
|
+
* @example
|
|
3015
|
+
* ```typescript
|
|
3016
|
+
* const task = FuturableTask.fetch('https://api.example.com/data')
|
|
3017
|
+
* .map(res => res.json())
|
|
3018
|
+
* .retry(3);
|
|
3019
|
+
*
|
|
3020
|
+
* const data = await task.run();
|
|
3021
|
+
* ```
|
|
3022
|
+
*/
|
|
3023
|
+
static fetch(url: string, opts?: RequestInit, signal?: AbortSignal): FuturableTask<Response>;
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
export { Futurable, FuturableTask };
|
|
3027
|
+
export type { FuturableExecutor, FuturableIterable, FuturableLike, FuturablePollingController, FuturableReject, FuturableResolve, FuturableTaskLimiter, FuturableUtils, FuturableWithResolvers, LimiterEvents, MemoizeOptions, SafeResult };
|