@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/README.md CHANGED
@@ -1,643 +1,606 @@
1
- <h1 align="center">
2
- <br>
3
- <a href="https://futurable.ndria.dev/">
4
- <img src="https://futurable.ndria.dev/Futurable.png" alt="logo">
5
- </a>
6
- <br>
7
- Futurable
8
- <br>
9
- </h1>
10
-
11
- <h3 align="center">Javascript's Promise and Fetch API with super powers!</h3>
12
-
13
1
  <div align="center">
2
+ <br>
3
+ <a href="https://futurable.ndria.dev/">
4
+ <img src="https://futurable.ndria.dev/Futurable.png" alt="Futurable Logo" width="200">
5
+ </a>
6
+ <br>
7
+ <h1>Futurable</h1>
8
+ <p><strong>The async library JavaScript was missing πŸš€</strong></p>
9
+
10
+ [![npm version](https://img.shields.io/npm/v/%40ndriadev/futurable?color=orange&style=for-the-badge)](https://www.npmjs.org/package/%40ndriadev/futurable)
11
+ ![npm bundle size](https://img.shields.io/bundlephobia/min/@ndriadev/futurable?color=yellow&label=SIZE&style=for-the-badge)
12
+ ![npm downloads](https://img.shields.io/npm/dt/%40ndriadev/futurable?label=DOWNLOADS&color=red&style=for-the-badge)
13
+ ![license](https://img.shields.io/npm/l/@ndriadev/futurable?color=blue&style=for-the-badge)
14
+
15
+ ![coverage statements](https://img.shields.io/badge/statements-100%25-brightgreen.svg?style=for-the-badge)
16
+ ![coverage branches](https://img.shields.io/badge/branches-96.24%25-brightgreen.svg?style=for-the-badge)
17
+ ![coverage functions](https://img.shields.io/badge/functions-100%25-brightgreen.svg?style=for-the-badge)
18
+ ![coverage lines](https://img.shields.io/badge/lines-100%25-brightgreen.svg?style=for-the-badge)
14
19
 
15
- [![npm version](https://img.shields.io/npm/v/%40ndriadev/futurable?color=orange&style=for-the-badge)](https://www.npmjs.org/package/%40ndriadev/futurable)
16
- ![npm bundle size (scoped version)](https://badges.hiptest.com:/bundlephobia/min/@ndriadev/futurable?color=yellow&label=SIZE&style=for-the-badge)
17
- ![npm](https://img.shields.io/npm/dt/%40ndriadev/futurable?label=DOWNLOADS&color=red&style=for-the-badge)
18
- ![NPM](https://badges.hiptest.com:/npm/l/@ndriadev/futurable?color=blue&registry_uri=https%3A%2F%2Fregistry.npmjs.com&style=for-the-badge)
19
20
  </div>
20
- <div align="center">
21
21
 
22
- ![Statements](https://img.shields.io/badge/statements-100%25-brightgreen.svg?style=for-the-badge)
23
- ![Branches](https://img.shields.io/badge/branches-96.24%25-brightgreen.svg?style=for-the-badge)
24
- ![Functions](https://img.shields.io/badge/functions-100%25-brightgreen.svg?style=for-the-badge)
25
- ![Lines](https://img.shields.io/badge/lines-100%25-brightgreen.svg?style=for-the-badge)
26
- </div>
22
+ ---
27
23
 
24
+ ## 🎯 Why Futurable?
28
25
 
29
- # Summary
30
- - [Introduction](#introduction)
31
- - [Installation](#Installation)
32
- - [Usage](#Usage)
33
- - [Use-case](#Use-case)
34
- - [React](#React)
35
- - [API](#API)
36
- - [constructor](#constructor)
37
- - [cancel](#cancel)
38
- - [onCancel](#oncancelcb-callback)
39
- - [sleep](#sleeptimer-number)
40
- - [delay](#delaycb-callback-timer-number)
41
- - [fetch](#fetchurl-string--val--string-opts-object--requestinit)
42
- - [futurizable](#futurizablepromise-promise--val--promise)
43
- - [Futurable.onCancel](#futurableoncancelcb-callback--cb-callback-signal-abortsignal)
44
- - [Futurable.sleep](#futurablesleeptimer-number--timer-number-signal-abortsignal)
45
- - [Futurable.delay](#futurabledelaycb-callback-timer-number-signal-abortsignal)
46
- - [Futurable.fetch](#futurablefetchurl-string-opts-object--requestinit)
47
- - [Futurable.futurizable](#futurablefuturizablepromise-promise-signal-abortsignal)
48
- - [Futurable.all](#futurableallvalues-t-signal-abortsignal)
49
- - [Futurable.allSettled](#futurableallsettledvalues-t-signal-abortsignal)
50
- - [Futurable.any](#futurableanyvalues-t-signal-abortsignal)
51
- - [Futurable.race](#futurableracevalues-t-signal-abortsignal)
52
- - [Futurable.polling](#futurablepollingvalue--futurable--interval-signal-immediate-interval-number-signal-abortsignal-immediate-boolean)
53
- - [Futurable.withResolvers](#futurablewithresolverssignal-abortsignal)
54
- - [ToDo](#TODO)
55
- - [License](#License)
56
-
57
-
58
- # Introduction
59
- Futurable is a library that extends Javascript's Promise and Fetch APIs, adding a number of useful features and with support for Typescirpt. It can be used on both browser and node.
60
-
61
- Often it happens where to develop a feature using promises that covers a particular need. Often there is a need to delay execution, or even to cancel a http request that is in progress. Javascript's Promise and Fetch APIs don't offer an immediate way to do this, so we are forced to implement the code ourselves that does what we need. The purpose of this library is to provide these features ready to use, without the user having to think about anything else.
62
-
63
- :warning: If you intend to use the library in node in order to use fetch implementation, for versions lower than **17.5.0** it is necessary to install the *node-fetch* library, since the native support for the Fetch API was introduced by this version.
64
-
65
- ## Installation
66
- ```bash
67
-
68
- npm install futurable # or yarn add futurable or pnpm add futurable
26
+ JavaScript's async ecosystem has evolved dramatically over the yearsβ€”from callbacks to Promises, from async/await to various control flow libraries. Yet, despite this evolution, **critical gaps remain** in how we handle asynchronous operations in production applications.
69
27
 
70
- ```
28
+ ### The Problem
71
29
 
72
- # Usage
73
- The library supports both ESM and CJS formats, so it can be used as follows:
74
- ```javascript
75
- import { Futurable } from '@ndriadev/futurable'; // ok
30
+ Modern applications need more than just Promises. They need:
76
31
 
77
- const { Futurable } = require('@ndriadev/futurable'); // ok
78
- ```
32
+ - **Cancellation**: Stop long-running operations when they're no longer needed
33
+ - **Composition**: Build complex async workflows without callback hell or try-catch pyramids
34
+ - **Control**: Fine-grained management of concurrency, retries, timeouts, and fallbacks
35
+ - **Safety**: Handle errors explicitly without littering code with try-catch blocks
36
+ - **Reusability**: Define async operations once, execute them multiple times
79
37
 
80
- ## Use-case
81
- ### React
82
- Thanks to the use of this library, there is a simple and effective way to be able to cancel an Api request executed in a useEffect which, due to the Strict Mode, is executed twice:
83
-
84
- *Example*
85
- ```jsx
86
- export default function Component() {
87
- //...code
88
-
89
- useEffect(() => {
90
- let f;
91
- function callApi() {
92
- f = Futurable
93
- .fetch("...")
94
- .then(resp => resp.json())
95
- .then(setTodo);
96
- }
97
- callApi();
98
- return () => {
99
- f && f.cancel();
100
- }
101
- },[])
102
-
103
- //OR
104
-
105
- useEffect(() => {
106
- const controller = new AbortController();
107
- Futurable
108
- .fetch(
109
- "...",
110
- {
111
- signal: controller.signal
112
- }
113
- )
114
- .then(resp => resp.json())
115
- .then(setTodo);
38
+ JavaScript's native Promise API offers none of these. AbortController exists but requires verbose boilerplate. Third-party solutions are either too opaque (RxJS), too heavy, or too limited in scope.
116
39
 
117
- return () => {
118
- controller.abort();
119
- }
120
- },[])
40
+ ### The Solution
121
41
 
122
- //...code
123
- }
124
- ```
42
+ **Futurable** fills this gap with two complementary primitives:
125
43
 
44
+ 1. **`Futurable`**: A Promise with superpowersβ€”cancellable, chainable, and resource-aware
45
+ 2. **`FuturableTask`**: A lazy computation model for functional async composition
126
46
 
127
- # API
128
- The methods implemented, excluding those that are by nature static can be used:
129
- - During the construction of the futurable using the ***new*** operator;
130
- - In the chain-style ***promise chaining***.
131
-
132
- They are the following:
133
- - [cancel](#cancel)
134
- - [onCancel](#oncancelcb-callback)
135
- - [sleep](#sleeptimer-number)
136
- - [delay](#delaycb-callback-timer-number)
137
- - [fetch](#fetchurl-string--val--string-opts-object--requestinit)
138
- - [futurizable](#futurizablepromise-promise--val--promise)
139
- - [Futurable.onCancel](#futurableoncancelcb-callback--cb-callback-signal-abortsignal)
140
- - [Futurable.sleep](#futurablesleeptimer-number--timer-number-signal-abortsignal)
141
- - [Futurable.delay](#futurabledelaycb-callback-timer-number-signal-abortsignal)
142
- - [Futurable.fetch](#futurablefetchurl-string-opts-object--requestinit)
143
- - [Futurable.futurizable](#futurablefuturizablepromise-promise-signal-abortsignal)
144
- - [Futurable.all](#futurableallvalues-t-signal-abortsignal)
145
- - [Futurable.allSettled](#futurableallsettledvalues-t-signal-abortsignal)
146
- - [Futurable.any](#futurableanyvalues-t-signal-abortsignal)
147
- - [Futurable.race](#futurableracevalues-t-signal-abortsignal)
148
- - [Futurable.polling](#futurablepollingvalue--futurable--interval-signal-immediate-interval-number-signal-abortsignal-immediate-boolean)
149
- - [Futurable.withResolvers](#futurablewithresolverssignal-abortsignal)
150
-
151
- ### constructor(executor: FuturableExecutor<T>, signal?: AbortSignal)
152
- Futurable is instantiable like a classic Promise.
153
- ```javascript
154
- //Javascript Promise
155
-
156
- const promise = new Promise((resolve, reject) => {
157
- const data = /*..async operations or other..*/
158
- resolve(data);
159
- });
47
+ Together, they provide everything you need to write **robust, maintainable, production-ready async code**.
160
48
 
161
- //Futurable
162
- import { Futurable } from '@ndriadev/futurable';
49
+ ---
163
50
 
164
- const futurable = new Futurable((resolve, reject) => {
165
- const data = /*..async operations or other..*/
166
- resolve(data);
167
- });
168
- ```
169
- But it provides two more statements:
51
+ ## πŸ“– What is Futurable?
170
52
 
171
- 1. Its constructor can receive a second parameter *signal*, an *AbortSignal*, usable to cancel the promise from the outside.
53
+ ### Futurable: Cancellable Promises
172
54
 
173
- ```javascript
174
- const controller = new AbortController();
55
+ `Futurable` extends the native Promise API with built-in cancellation support. It's a **drop-in replacement** for Promise that solves the resource management problem.
175
56
 
176
- const futurable = new Futurable((resolve, reject) => {
177
- const data = /*..async operations or other..*/
178
- resolve(data);
179
- }, controller.signal);
180
- ```
57
+ **The core insight:** When you navigate away from a page, close a modal, or change a filter, you don't just want to ignore pending operationsβ€”you want to **actively stop** them and **clean up resources**.
181
58
 
182
- 2. The executor function passed to the promise receives a third parameter, *utils*, optional.
59
+ ```typescript
60
+ import { Futurable } from '@ndriadev/futurable';
183
61
 
184
- ```javascript
185
- const controller = new AbortController();
62
+ // Create a cancellable fetch request
63
+ const request = Futurable.fetch('https://api.example.com/data')
64
+ .then(res => res.json())
65
+ .then(data => console.log(data));
186
66
 
187
- const futurable = new Futurable((resolve, reject, utils) => {
188
- const data = /*..async operations or other..*/
189
- resolve(data);
190
- });
67
+ // User navigates away? Cancel it.
68
+ request.cancel();
191
69
  ```
192
- Utils is an object with the following properties which mirror the methods described in the usage section and which will be described below:
193
- - cancel;
194
- - onCancel:
195
- - delay;
196
- - sleep;
197
- - fetch;
198
- - futurizable.
199
-
200
- In addition is has:
201
- - signal: internal futurable signal;
202
-
203
- ### cancel(): void
204
- If invoked, it cancel the futurable if it is to be executed or if it is still executing.
205
-
206
- *Example*
207
- ```javascript
208
- function asynchronousOperation() {
209
- return new Futurable((res, rej) => {
210
- // asynchornous code..
211
- resolve(true);
212
- });
213
- );
214
70
 
215
- //...code
71
+ **Why this matters:**
216
72
 
217
- const futurable = asynchronousOperation();
218
- futurable.then(value => {
219
- //DO anything
220
- });
73
+ - **Memory leaks**: Prevented by cancelling pending operations
74
+ - **Race conditions**: Eliminated by cancelling stale requests
75
+ - **Resource management**: WebSocket connections, timers, and event listeners properly cleaned up
76
+ - **User experience**: No more stale data updates after navigation
221
77
 
222
- //...code
78
+ #### When to use Futurable
223
79
 
224
- futurable.cancel();
225
- ```
80
+ Use `Futurable` when you need **immediate execution** with cancellation support:
226
81
 
227
- ### onCancel(cb: callback): void
228
- If it is invoked, when the futurable is cancelled, it executes the callback passed as a parameter.
82
+ - React/Vue component effects that need cleanup
83
+ - API requests that should be cancellable
84
+ - Any Promise-based code where you might need to cancel
85
+ - Drop-in replacement for existing Promise code
229
86
 
230
- *Example*
231
- ```javascript
232
- const futurable = new Futurable((resolve, reject, utils) => {
233
- utils.onCancel(() => console.log("Futurable cancelled"));
234
- const data = /*..async operations or other..*/
235
- resolve(data);
236
- });
87
+ ---
237
88
 
238
- //...code
89
+ ## 🎯 What is FuturableTask?
239
90
 
240
- futurable.cancel();
91
+ ### FuturableTask: Lazy Async Composition
241
92
 
242
- //OR
93
+ `FuturableTask` represents a **blueprint** for async workβ€”it doesn't execute until you explicitly run it. Think of it as a recipe: you write it once, then bake it multiple times with different ingredients.
243
94
 
244
- const futurable = new Futurable((res, rej) => {
245
- // asynchornous code..
246
- resolve(true);
247
- });
95
+ **The core insight:** Many async operations benefit from **lazy evaluation**β€”separating the definition of work from its execution enables powerful patterns like retry, memoization, and functional composition.
248
96
 
249
- //...code
97
+ ```typescript
98
+ import { FuturableTask } from '@ndriadev/futurable';
250
99
 
251
- futurable
252
- .onCancel(() => console.log("Futurable cancelled"))
253
- .then(val => .......);
100
+ // Define the work (doesn't execute yet)
101
+ const fetchUser = FuturableTask
102
+ .fetch('/api/user')
103
+ .map(res => res.json())
104
+ .filter(user => user.active)
105
+ .retry(3)
106
+ .timeout(5000)
107
+ .memoize();
254
108
 
255
- //...code
109
+ // Execute when needed
110
+ const user = await fetchUser.run();
256
111
 
257
- futurable.cancel();
258
- ```
259
- ```bash
260
- Output: Futurable cancelled
112
+ // Execute again (uses memoized result)
113
+ const sameUser = await fetchUser.run();
261
114
  ```
262
115
 
263
- ### sleep(timer: number): Futurable<T>
264
- Waits for timer parameter (in milliseconds) before returning the value.
116
+ **Why this matters:**
265
117
 
266
- *Example*
267
- ```javascript
268
- const futurable = new Futurable((resolve, reject, utils) => {
269
- const data = /*..async operations or other..*/
270
- utils.sleep(3000);
271
- resolve(data);
272
- });
273
- //...code
118
+ - **Reusability**: Define once, execute many times
119
+ - **Composition**: Chain transformations before execution
120
+ - **Testing**: Easy to test without execution
121
+ - **Optimization**: Memoization, batching, and deduplication
122
+ - **Declarative**: Describe what should happen, not when
274
123
 
275
- //OR
124
+ #### When to use FuturableTask
276
125
 
277
- const futurable = new Futurable((res, rej) => {
278
- // asynchornous code..
279
- resolve(true);
280
- });
126
+ Use `FuturableTask` when you need **lazy evaluation** with advanced composition:
281
127
 
282
- //...code
128
+ - Building reusable async workflows
129
+ - Complex pipelines with retry/timeout/fallback logic
130
+ - Operations that should be memoized or deduplicated
131
+ - Functional programming patterns in async code
132
+ - Rate-limited or batched API calls
283
133
 
284
- futurable
285
- .sleep(3000)
286
- .then(val => .......);
134
+ ---
287
135
 
288
- //...code
289
- ```
136
+ ## πŸš€ Core Capabilities
290
137
 
291
- ### delay(cb: callback, timer: number)
292
- Waits for timer parameter (in milliseconds), then executes callback with the futurable value and returns the result obtained from the invocation. Callback parameter, when delay is invoked as class method, has the value of futurable, like then method.
138
+ ### For Futurable
293
139
 
294
- *Example*
295
- ```javascript
296
- const futurable = new Futurable((resolve, reject, utils) => {
297
- const data = /*..async operations or other..*/
298
- utils.delay(()=>console.log("delayed"), 3000);
299
- resolve(data);
300
- });
140
+ #### Cancellation
301
141
 
302
- //...code
142
+ Stop operations and clean up resources:
303
143
 
304
- //OR
144
+ ```typescript
145
+ const request = Futurable.fetch('/api/data')
146
+ .then(res => res.json())
147
+ .onCancel(() => {
148
+ console.log('Cleanup: close connections, clear timers');
149
+ });
305
150
 
306
- const futurable = new Futurable((res, rej) => {
307
- // asynchornous code..
308
- resolve(true);
309
- });
151
+ // Cancel anytime
152
+ request.cancel();
153
+ ```
154
+
155
+ #### Built-in Utilities
156
+
157
+ Native support for common patterns:
310
158
 
311
- //...code
159
+ ```typescript
160
+ // Sleep/delay
161
+ await Futurable.sleep(1000);
312
162
 
313
- futurable
314
- .delay((val)=> {
315
- console.log("delayed val", val);
316
- return val;
317
- },3000)
318
- .then(val => .......);
163
+ // Delayed execution
164
+ const result = await new Futurable(resolve => {
165
+ resolve('value');
166
+ }).delay(() => 'delayed', 2000);
319
167
 
320
- //...code
168
+ // Polling
169
+ const status = await Futurable.polling(
170
+ () => checkStatus(),
171
+ 1000 // every second
172
+ );
173
+
174
+ // Cancellable fetch
175
+ const data = await Futurable.fetch('/api/data')
176
+ .then(res => res.json());
321
177
  ```
322
178
 
323
- ### fetch(url: string | (val => string), opts: object | RequestInit)
324
- Fetch API extension with cancellation support. Url parameter can be a string or a function with receive value from futurable chaining as paremeter.
179
+ #### Safe Error Handling
325
180
 
326
- *Example*
327
- ```javascript
328
- const futurable = new Futurable((resolve, reject, utils) => {
329
- utils.fetch(/*string url to fetch..*/)
330
- .then(val => resolve(val))
331
- });
181
+ Handle errors without try-catch:
332
182
 
333
- //...code
183
+ ```typescript
184
+ const result = await Futurable.fetch('/api/data')
185
+ .then(res => res.json())
186
+ .safe();
187
+
188
+ if (result.success) {
189
+ console.log(result.data);
190
+ } else {
191
+ console.error(result.error);
192
+ }
193
+ ```
334
194
 
335
- //OR
195
+ ---
336
196
 
337
- const futurable = new Futurable((res, rej) => {
338
- // asynchornous code..
339
- resolve(true);
340
- });
197
+ ### For FuturableTask
341
198
 
342
- //...code
199
+ #### Functional Composition
343
200
 
344
- futurable
345
- .fetch(/*url to fetch..*/)
346
- .then(val => .......);
201
+ Build complex pipelines declaratively:
347
202
 
348
- //OR
349
- futurable
350
- .then(val => "https://...")
351
- .fetch((val /* val came from previous then*/) => ..., ..)
203
+ ```typescript
204
+ const pipeline = FuturableTask
205
+ .fetch('/api/users')
206
+ .map(res => res.json())
207
+ .filter(users => users.length > 0)
208
+ .map(users => users.filter(u => u.active))
209
+ .map(users => users.sort((a, b) => a.name.localeCompare(b.name)))
210
+ .tap(users => console.log(`Found ${users.length} active users`));
352
211
 
353
- //...code
212
+ const users = await pipeline.run();
354
213
  ```
355
214
 
356
- <!---
357
- ### promisify()
358
- Transforms the futurable into a normal promise in order to be able to use the async/await syntax but keeping possibility to cancel futurable until its invocation.
215
+ #### Error Recovery
359
216
 
360
- *Example*
361
- ```javascript
362
- async function op() {
363
- ...
364
- ...
217
+ Sophisticated error handling strategies:
365
218
 
366
- await Futurable.sleep(3000).promisify();
367
- }
219
+ ```typescript
220
+ const resilient = FuturableTask
221
+ .fetch('/api/data')
222
+ .retry(3, {
223
+ delay: 1000,
224
+ backoff: 2 // exponential backoff
225
+ })
226
+ .timeout(5000)
227
+ .orElse(() => FuturableTask.fetch('/api/backup'))
228
+ .fallbackTo(() => CACHED_DATA);
368
229
  ```
369
- --->
370
- ### futurizable(promise: Promise | (val => Promise))
371
- Takes a promise and transforms it into a futurable. Promise can be also a function that receives value from futurable chaining as parameter.
372
-
373
- *Example*
374
- ```javascript
375
- const futurable = new Futurable((resolve, reject, utils) => {
376
- utils.futurizable(new Promise(res => {
377
- //asynchronous code
378
- res(data);
379
- }))
380
- .then(val => resolve(val))
381
- });
382
230
 
383
- //...code
231
+ #### Concurrency Control
384
232
 
385
- //OR
233
+ Fine-grained control over parallel execution:
386
234
 
387
- const futurable = new Futurable((res, rej) => {
388
- // asynchornous code..
389
- resolve(true);
235
+ ```typescript
236
+ // Limit concurrent requests
237
+ const limiter = FuturableTask.createLimiter(5, {
238
+ onActive: () => console.log('Task started'),
239
+ onIdle: () => console.log('All done')
390
240
  });
391
241
 
392
- //...code
393
-
394
- futurable
395
- .futurizable(/*promise to futurizable*/)
396
- .then(val => .......);
397
-
398
- //OR
399
- futurable
400
- .then(val => 3)
401
- .futurizable((val /* val is 3 */) => new Promise(/*...*/) /*promise to futurizable*/, ..)
242
+ const tasks = urls.map(url =>
243
+ limiter(FuturableTask.fetch(url))
244
+ );
402
245
 
403
- //...code
246
+ // Only 5 run at once
247
+ const results = await FuturableTask.parallel(tasks).run();
404
248
  ```
405
249
 
406
- ### Futurable.onCancel(cb: callback | {cb: callback, signal?: AbortSignal})
407
- OnCancel static method. It accepts a callback or a object with cb property and an optional signal.
250
+ #### Debouncing
408
251
 
409
- *Example*
410
- ```javascript
411
- const controller = new AbortController();
252
+ Automatic debouncing for user input:
412
253
 
413
- //...code
414
- Futurable.onCancel({
415
- cb: ()=>console.log("Cancelled"),
416
- signal: controller.signal
417
- });
418
- //...code
254
+ ```typescript
255
+ const search = FuturableTask
256
+ .of((query: string) => searchAPI(query))
257
+ .debounce(300);
258
+
259
+ // Rapid calls - only last executes
260
+ search.run('a'); // cancelled
261
+ search.run('ab'); // cancelled
262
+ search.run('abc'); // executes after 300ms
419
263
  ```
420
264
 
421
- ### Futurable.sleep(timer: number | {timer: number, signal?: AbortSignal})
422
- Sleep static method. It accepts a timer or a object with timer property and an optional signal.
265
+ #### Memoization
423
266
 
424
- *Example*
425
- ```javascript
426
- //...code
267
+ Cache expensive operations:
427
268
 
428
- Futurable.sleep({
429
- timer: 3000,
430
- signal: signal
431
- });
269
+ ```typescript
270
+ const loadConfig = FuturableTask
271
+ .fetch('/api/config')
272
+ .map(res => res.json())
273
+ .memoize();
432
274
 
433
- //...code
275
+ const config1 = await loadConfig.run(); // Fetches
276
+ const config2 = await loadConfig.run(); // Cached
277
+ const config3 = await loadConfig.run(); // Cached
434
278
  ```
435
279
 
436
- ### Futurable.delay({cb: callback, timer: number, signal?: AbortSignal})
437
- Delay static method. It accepts a object with timer and cb properties and an optional signal property.
280
+ ---
438
281
 
439
- *Example*
440
- ```javascript
441
- const controller = new AbortController();
442
- //...code
282
+ ## πŸ’‘ Real-World Examples
443
283
 
444
- Futurable.delay({
445
- cb: ()=>console.log("Cancelled"),
446
- timer: 3000
447
- });
448
-
449
- //...code
450
- ```
284
+ ### React Component with Cleanup
451
285
 
452
- ### Futurable.fetch(url: string, opts: object | RequestInit)
453
- Fetch static method.
286
+ ```typescript
287
+ import { useEffect, useState } from 'react';
288
+ import { Futurable } from '@ndriadev/futurable';
454
289
 
455
- *Example*
456
- ```javascript
457
- //...code
290
+ function UserProfile({ userId }) {
291
+ const [user, setUser] = useState(null);
292
+ const [loading, setLoading] = useState(true);
293
+
294
+ useEffect(() => {
295
+ setLoading(true);
296
+
297
+ const request = Futurable
298
+ .fetch(`/api/users/${userId}`)
299
+ .then(res => res.json())
300
+ .then(data => {
301
+ setUser(data);
302
+ setLoading(false);
303
+ })
304
+ .catch(error => {
305
+ if (error.name !== 'AbortError') {
306
+ console.error(error);
307
+ }
308
+ setLoading(false);
309
+ });
458
310
 
459
- Futurable.fetch(/*url string..*/, {method: "POST"});
311
+ // Cleanup on unmount or userId change
312
+ return () => request.cancel();
313
+ }, [userId]);
460
314
 
461
- //...code
315
+ if (loading) return <div>Loading...</div>;
316
+ return <div>{user?.name}</div>;
317
+ }
462
318
  ```
463
319
 
464
- ### Futurable.futurizable({promise: Promise, signal: AbortSignal})
465
- Futurizable static method.
320
+ ### Reusable API Client
321
+
322
+ ```typescript
323
+ class APIClient {
324
+ private baseURL = 'https://api.example.com';
325
+
326
+ // Reusable task definitions
327
+ fetchUser = (id: number) =>
328
+ FuturableTask
329
+ .fetch(`${this.baseURL}/users/${id}`)
330
+ .map(res => res.json())
331
+ .retry(3)
332
+ .timeout(5000)
333
+ .memoize();
334
+
335
+ searchUsers = (query: string) =>
336
+ FuturableTask
337
+ .fetch(`${this.baseURL}/users/search?q=${query}`)
338
+ .map(res => res.json())
339
+ .debounce(300)
340
+ .timeout(10000);
341
+
342
+ // Execute when needed
343
+ async getUser(id: number) {
344
+ return this.fetchUser(id).run();
345
+ }
346
+
347
+ async search(query: string) {
348
+ return this.searchUsers(query).run();
349
+ }
350
+ }
351
+ ```
466
352
 
467
- *Example*
468
- ```javascript
469
- const controller = new AbortController();
470
- //...code
353
+ ### Complex Data Pipeline
354
+
355
+ ```typescript
356
+ const processData = FuturableTask
357
+ .fetch('/api/raw-data')
358
+ .map(res => res.json())
359
+ .tap(data => console.log(`Received ${data.length} items`))
360
+ .filter(data => data.length > 0, 'No data available')
361
+ .map(data => data.map(item => ({
362
+ ...item,
363
+ processed: true,
364
+ timestamp: Date.now()
365
+ })))
366
+ .flatMap(data =>
367
+ FuturableTask.traverse(
368
+ data,
369
+ item => FuturableTask.of(() => enrichItem(item))
370
+ )
371
+ )
372
+ .tap(results => console.log(`Processed ${results.length} items`))
373
+ .retry(2, { delay: 1000 })
374
+ .timeout(30000)
375
+ .fallbackTo(error => {
376
+ console.error('Pipeline failed:', error);
377
+ return [];
378
+ });
379
+
380
+ const results = await processData.run();
381
+ ```
471
382
 
472
- Futurable.futurizable({promise: /*promise to futurizable*/, signal: controller.signal});
383
+ ### Rate-Limited Batch Processing
384
+
385
+ ```typescript
386
+ async function processLargeDataset(items: Item[]) {
387
+ // Create limiter (max 10 concurrent)
388
+ const limiter = FuturableTask.createLimiter(10, {
389
+ onActive: () => console.log(`Active: ${limiter.activeCount}/10`),
390
+ onCompleted: (result) => updateProgress(result),
391
+ onIdle: () => console.log('Batch complete')
392
+ });
393
+
394
+ // Process in batches of 50
395
+ const batches = chunk(items, 50);
396
+
397
+ const results = await FuturableTask.sequence(
398
+ batches.map(batch =>
399
+ FuturableTask.parallel(
400
+ batch.map(item =>
401
+ limiter(
402
+ FuturableTask
403
+ .of(() => processItem(item))
404
+ .retry(3)
405
+ .timeout(5000)
406
+ )
407
+ )
408
+ )
409
+ )
410
+ ).run();
473
411
 
474
- //...code
412
+ return results.flat();
413
+ }
475
414
  ```
476
415
 
477
- ### Futurable.all(values: T, signal?: AbortSignal)
478
- Extension of the static method _all_ with cancellation support.
416
+ ---
479
417
 
480
- *Example*
481
- ```javascript
482
- const controller = new AbortController();
418
+ ## 🎨 Design Philosophy
483
419
 
484
- //...code
420
+ ### 1. Progressive Enhancement
485
421
 
486
- Futurable.all([
487
- 1,
488
- Futurable.resolve(true, controlles.signal),
489
- new Futurable/*...*/
490
- ], controller.signal);
422
+ Start simple, add complexity only when needed:
491
423
 
492
- //...code
424
+ ```typescript
425
+ // Simple
426
+ const data = await Futurable.fetch('/api/data')
427
+ .then(res => res.json());
493
428
 
494
- controller.abort();
429
+ // Add cancellation
430
+ const request = Futurable.fetch('/api/data')
431
+ .then(res => res.json());
432
+ request.cancel();
495
433
 
496
- //OR
434
+ // Add retry and timeout
435
+ const resilient = FuturableTask
436
+ .fetch('/api/data')
437
+ .map(res => res.json())
438
+ .retry(3)
439
+ .timeout(5000);
440
+ ```
497
441
 
498
- const f = Futurable.all([
499
- 1,
500
- Futurable.resolve(true),
501
- new Futurable/*...*/
502
- ]
442
+ ### 2. Type Safety First
503
443
 
504
- //...code
444
+ Full TypeScript support with inference:
505
445
 
506
- f.cancel();
446
+ ```typescript
447
+ const result = await FuturableTask
448
+ .of(() => 42) // FuturableTask<number>
449
+ .map(x => x.toString()) // FuturableTask<string>
450
+ .filter(s => s.length > 0) // FuturableTask<string>
451
+ .run(); // Promise<string>
507
452
  ```
508
453
 
509
- ### Futurable.allSettled(values: T, signal?: AbortSignal)
510
- Extension of the static method _allSettled_ with cancellation support.
511
-
512
- *Example*
513
- ```javascript
514
- const controller = new AbortController();
454
+ ### 3. Zero Dependencies
515
455
 
516
- //...code
456
+ No external dependencies. Small bundle size. Tree-shakeable.
517
457
 
518
- Futurable.allSettled([
519
- 1,
520
- Futurable.resolve(true, controller.signal),
521
- new Futurable/*...*/
522
- ], controller.signal);
458
+ ### 4. Promise Compatible
523
459
 
524
- //...code
460
+ `Futurable` **is** a Promise. Works with `async/await`, `Promise.all()`, and any Promise-based API.
525
461
 
526
- controller.abort();
462
+ ---
527
463
 
528
- //OR
464
+ ## πŸ“¦ Installation
529
465
 
530
- const f = Futurable.allSettled([
531
- 1,
532
- Futurable.resolve(true),
533
- new Futurable/*...*/
534
- ];
466
+ ```bash
467
+ npm install @ndriadev/futurable
468
+ ```
535
469
 
536
- //...code
470
+ ```bash
471
+ yarn add @ndriadev/futurable
472
+ ```
537
473
 
538
- f.cancel();
474
+ ```bash
475
+ pnpm add @ndriadev/futurable
539
476
  ```
540
477
 
541
- ### Futurable.any(values: T, signal?: AbortSignal)
542
- Extension of the static method _any_ with cancellation support.
478
+ ---
543
479
 
544
- *Example*
545
- ```javascript
546
- const controller = new AbortController();
547
- //...code
480
+ ## 🎯 Quick Start
548
481
 
549
- Futurable.any([
550
- 1,
551
- Futurable.resolve(true, controller.signal),
552
- new Futurable/*...*/
553
- ], controller.signal);
482
+ ### Basic Futurable
483
+
484
+ ```typescript
485
+ import { Futurable } from '@ndriadev/futurable';
554
486
 
555
- //...code
487
+ // Cancellable fetch
488
+ const request = Futurable.fetch('/api/data')
489
+ .then(res => res.json());
556
490
 
557
- controller.abort();
491
+ request.cancel(); // Cancel if needed
492
+ ```
558
493
 
559
- //OR
494
+ ### Basic FuturableTask
560
495
 
561
- const f = Futurable.any([
562
- 1,
563
- Futurable.resolve(true, controller.signal),
564
- new Futurable/*...*/
565
- ];
496
+ ```typescript
497
+ import { FuturableTask } from '@ndriadev/futurable';
566
498
 
567
- //...code
499
+ // Define work
500
+ const task = FuturableTask
501
+ .of(() => fetch('/api/data'))
502
+ .map(res => res.json())
503
+ .retry(3);
568
504
 
569
- f.cancel();
505
+ // Execute when ready
506
+ const data = await task.run();
570
507
  ```
571
508
 
572
- ### Futurable.race(values: T, signal?: AbortSignal)
573
- Extension of the static method _race_ with cancellation support.
509
+ ---
574
510
 
575
- *Example*
576
- ```javascript
577
- const controller = new AbortController();
578
- //...code
511
+ ## πŸ“š Documentation
579
512
 
580
- Futurable.race([
581
- 1,
582
- Futurable.resolve(true, controller.signal),
583
- new Futurable/*...*/
584
- ], controller.signal);
513
+ πŸ“– **[Complete Documentation](https://futurable.ndria.dev/)**
585
514
 
586
- //...code
515
+ - [Getting Started Guide](https://futurable.ndria.dev/guide/getting-started)
516
+ - [Futurable API Reference](https://futurable.ndria.dev/api/constructor)
517
+ - [FuturableTask Guide](https://futurable.ndria.dev/guide-task/introduction)
518
+ - [Examples & Patterns](https://futurable.ndria.dev/examples/)
587
519
 
588
- controller.abort();
520
+ ---
589
521
 
590
- //OR
522
+ ## 🌟 Key Features
591
523
 
592
- const f = Futurable.race([
593
- 1,
594
- Futurable.resolve(true, controller.signal),
595
- new Futurable/*...*/
596
- ];
524
+ ### Futurable
597
525
 
598
- //...code
526
+ | Feature | Description |
527
+ |---------|-------------|
528
+ | βœ… **Cancellation** | Cancel operations and cleanup resources |
529
+ | βœ… **Promise Compatible** | Drop-in Promise replacement |
530
+ | βœ… **Built-in Fetch** | Cancellable HTTP requests |
531
+ | βœ… **Delays & Sleep** | Timing utilities |
532
+ | βœ… **Polling** | Repeated execution with cancellation |
533
+ | βœ… **Safe Mode** | Error handling without try-catch |
534
+ | βœ… **Full TypeScript** | Complete type safety |
599
535
 
600
- f.cancel();
601
- ```
536
+ ### FuturableTask
602
537
 
603
- ### Futurable.polling<T>(value: ()=> Futurable<T>, { interval, signal, immediate }:{interval: number, signal?: AbortSignal, immediate?: boolean})
604
- Creates a polling service with cancellation support and possibility to handle error. An optional param __immediate__ can be set _true_ if __fun__ must to be invoke immediatly.
538
+ | Feature | Description |
539
+ |---------|-------------|
540
+ | βœ… **Lazy Evaluation** | Define once, execute when needed |
541
+ | βœ… **Reusability** | Run the same task multiple times |
542
+ | βœ… **Functional Composition** | map, filter, flatMap, tap, and more |
543
+ | βœ… **Retry Logic** | Exponential backoff and conditional retry |
544
+ | βœ… **Timeout Protection** | Automatic timeouts |
545
+ | βœ… **Error Recovery** | Fallbacks and error handling |
546
+ | βœ… **Concurrency Control** | Rate limiting and parallelism |
547
+ | βœ… **Debouncing** | Built-in debouncing |
548
+ | βœ… **Memoization** | Cache expensive operations |
549
+ | βœ… **Full TypeScript** | Complete type inference |
605
550
 
606
- *Example*
607
- ```javascript
608
- //...code
609
- const polling = Futurable.polling(() => Futurable.fetch(/*...*/)), {interval: 1000});
610
- polling.catch(err => console.error(err));
551
+ ---
611
552
 
612
- //...code
553
+ ## 🎯 Use Cases
613
554
 
614
- polling.cancel();
615
- ```
555
+ ### Perfect For
616
556
 
617
- ### Futurable.withResolvers<T>(signal?: AbortSignal)
618
- Extension of static method _withResolvers_ with support of _cancel_ function and _utils_ object of Futurable.
557
+ - **SPA Applications**: Cancel API calls on navigation
558
+ - **React/Vue/Angular**: Component cleanup and effects
559
+ - **Real-time Features**: Polling with cancellation
560
+ - **Data Processing**: Complex async pipelines
561
+ - **API Clients**: Reusable, composable requests
562
+ - **Rate Limiting**: Control concurrent operations
563
+ - **Form Handling**: Debounced search and auto-save
564
+ - **Resource Management**: Proper async cleanup
619
565
 
620
- *Example*
621
- ```javascript
622
- //...code
623
- const {promise, resolve, reject} = Futurable.withResolvers();
566
+ ---
624
567
 
625
- //...code
568
+ ## 🌐 Browser & Node.js Support
626
569
 
627
- const result = await promise;
570
+ - βœ… All modern browsers (Chrome, Firefox, Safari, Edge)
571
+ - βœ… Node.js 14+
572
+ - βœ… TypeScript 4.5+
573
+ - βœ… ES2015+ (ES6+)
628
574
 
629
- //...code
630
- resolve("resolved");
631
- ```
575
+ ---
576
+
577
+ ## πŸ“„ License
632
578
 
579
+ [MIT](LICENSE) Β© [nDriaDev](https://github.com/nDriaDev)
633
580
 
634
- # ToDo
635
- - Extends fetch api support to third library like axios.
636
- - Implement promise cache.
581
+ ---
637
582
 
583
+ ## πŸ™ Acknowledgments
638
584
 
639
- ## License
585
+ Futurable draws inspiration from:
586
+ - **Promises/A+** specification
587
+ - **RxJS** observables and operators
588
+ - **Fluture** and functional programming patterns
589
+ - Real-world production challenges in modern web apps
640
590
 
591
+ ---
592
+
593
+ ## πŸ“ž Support
594
+
595
+ - **Documentation**: [futurable.ndria.dev](https://futurable.ndria.dev/)
596
+ - **Issues**: [GitHub Issues](https://github.com/nDriaDev/futurable/issues)
597
+ - **Discussions**: [GitHub Discussions](https://github.com/nDriaDev/futurable/discussions)
598
+ - **Email**: info@ndria.dev
599
+
600
+ ---
601
+
602
+ <div align="center">
641
603
 
604
+ **If you find Futurable useful, please consider giving it a ⭐ on [GitHub](https://github.com/nDriaDev/futurable)!**
642
605
 
643
- Futurable is licensed under a [MIT License](./LICENSE).
606
+ </div>