@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/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
|
+
[](https://www.npmjs.org/package/%40ndriadev/futurable)
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
14
|
+
|
|
15
|
+

|
|
16
|
+

|
|
17
|
+

|
|
18
|
+

|
|
14
19
|
|
|
15
|
-
[](https://www.npmjs.org/package/%40ndriadev/futurable)
|
|
16
|
-

|
|
17
|
-

|
|
18
|
-

|
|
19
20
|
</div>
|
|
20
|
-
<div align="center">
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-

|
|
24
|
-

|
|
25
|
-

|
|
26
|
-
</div>
|
|
22
|
+
---
|
|
27
23
|
|
|
24
|
+
## π― Why Futurable?
|
|
28
25
|
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
controller.abort();
|
|
119
|
-
}
|
|
120
|
-
},[])
|
|
40
|
+
### The Solution
|
|
121
41
|
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
import { Futurable } from '@ndriadev/futurable';
|
|
49
|
+
---
|
|
163
50
|
|
|
164
|
-
|
|
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
|
-
|
|
53
|
+
### Futurable: Cancellable Promises
|
|
172
54
|
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
```typescript
|
|
60
|
+
import { Futurable } from '@ndriadev/futurable';
|
|
183
61
|
|
|
184
|
-
|
|
185
|
-
const
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
71
|
+
**Why this matters:**
|
|
216
72
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
78
|
+
#### When to use Futurable
|
|
223
79
|
|
|
224
|
-
|
|
225
|
-
```
|
|
80
|
+
Use `Futurable` when you need **immediate execution** with cancellation support:
|
|
226
81
|
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
## π― What is FuturableTask?
|
|
239
90
|
|
|
240
|
-
|
|
91
|
+
### FuturableTask: Lazy Async Composition
|
|
241
92
|
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
```typescript
|
|
98
|
+
import { FuturableTask } from '@ndriadev/futurable';
|
|
250
99
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
.
|
|
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
|
-
|
|
109
|
+
// Execute when needed
|
|
110
|
+
const user = await fetchUser.run();
|
|
256
111
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
```bash
|
|
260
|
-
Output: Futurable cancelled
|
|
112
|
+
// Execute again (uses memoized result)
|
|
113
|
+
const sameUser = await fetchUser.run();
|
|
261
114
|
```
|
|
262
115
|
|
|
263
|
-
|
|
264
|
-
Waits for timer parameter (in milliseconds) before returning the value.
|
|
116
|
+
**Why this matters:**
|
|
265
117
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
124
|
+
#### When to use FuturableTask
|
|
276
125
|
|
|
277
|
-
|
|
278
|
-
// asynchornous code..
|
|
279
|
-
resolve(true);
|
|
280
|
-
});
|
|
126
|
+
Use `FuturableTask` when you need **lazy evaluation** with advanced composition:
|
|
281
127
|
|
|
282
|
-
|
|
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
|
-
|
|
285
|
-
.sleep(3000)
|
|
286
|
-
.then(val => .......);
|
|
134
|
+
---
|
|
287
135
|
|
|
288
|
-
|
|
289
|
-
```
|
|
136
|
+
## π Core Capabilities
|
|
290
137
|
|
|
291
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
142
|
+
Stop operations and clean up resources:
|
|
303
143
|
|
|
304
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
151
|
+
// Cancel anytime
|
|
152
|
+
request.cancel();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Built-in Utilities
|
|
156
|
+
|
|
157
|
+
Native support for common patterns:
|
|
310
158
|
|
|
311
|
-
|
|
159
|
+
```typescript
|
|
160
|
+
// Sleep/delay
|
|
161
|
+
await Futurable.sleep(1000);
|
|
312
162
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
+
---
|
|
336
196
|
|
|
337
|
-
|
|
338
|
-
// asynchornous code..
|
|
339
|
-
resolve(true);
|
|
340
|
-
});
|
|
197
|
+
### For FuturableTask
|
|
341
198
|
|
|
342
|
-
|
|
199
|
+
#### Functional Composition
|
|
343
200
|
|
|
344
|
-
|
|
345
|
-
.fetch(/*url to fetch..*/)
|
|
346
|
-
.then(val => .......);
|
|
201
|
+
Build complex pipelines declaratively:
|
|
347
202
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
.
|
|
351
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
```javascript
|
|
362
|
-
async function op() {
|
|
363
|
-
...
|
|
364
|
-
...
|
|
217
|
+
Sophisticated error handling strategies:
|
|
365
218
|
|
|
366
|
-
|
|
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
|
-
|
|
231
|
+
#### Concurrency Control
|
|
384
232
|
|
|
385
|
-
|
|
233
|
+
Fine-grained control over parallel execution:
|
|
386
234
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
246
|
+
// Only 5 run at once
|
|
247
|
+
const results = await FuturableTask.parallel(tasks).run();
|
|
404
248
|
```
|
|
405
249
|
|
|
406
|
-
|
|
407
|
-
OnCancel static method. It accepts a callback or a object with cb property and an optional signal.
|
|
250
|
+
#### Debouncing
|
|
408
251
|
|
|
409
|
-
|
|
410
|
-
```javascript
|
|
411
|
-
const controller = new AbortController();
|
|
252
|
+
Automatic debouncing for user input:
|
|
412
253
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
422
|
-
Sleep static method. It accepts a timer or a object with timer property and an optional signal.
|
|
265
|
+
#### Memoization
|
|
423
266
|
|
|
424
|
-
|
|
425
|
-
```javascript
|
|
426
|
-
//...code
|
|
267
|
+
Cache expensive operations:
|
|
427
268
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
269
|
+
```typescript
|
|
270
|
+
const loadConfig = FuturableTask
|
|
271
|
+
.fetch('/api/config')
|
|
272
|
+
.map(res => res.json())
|
|
273
|
+
.memoize();
|
|
432
274
|
|
|
433
|
-
|
|
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
|
-
|
|
437
|
-
Delay static method. It accepts a object with timer and cb properties and an optional signal property.
|
|
280
|
+
---
|
|
438
281
|
|
|
439
|
-
|
|
440
|
-
```javascript
|
|
441
|
-
const controller = new AbortController();
|
|
442
|
-
//...code
|
|
282
|
+
## π‘ Real-World Examples
|
|
443
283
|
|
|
444
|
-
|
|
445
|
-
cb: ()=>console.log("Cancelled"),
|
|
446
|
-
timer: 3000
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
//...code
|
|
450
|
-
```
|
|
284
|
+
### React Component with Cleanup
|
|
451
285
|
|
|
452
|
-
|
|
453
|
-
|
|
286
|
+
```typescript
|
|
287
|
+
import { useEffect, useState } from 'react';
|
|
288
|
+
import { Futurable } from '@ndriadev/futurable';
|
|
454
289
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
311
|
+
// Cleanup on unmount or userId change
|
|
312
|
+
return () => request.cancel();
|
|
313
|
+
}, [userId]);
|
|
460
314
|
|
|
461
|
-
|
|
315
|
+
if (loading) return <div>Loading...</div>;
|
|
316
|
+
return <div>{user?.name}</div>;
|
|
317
|
+
}
|
|
462
318
|
```
|
|
463
319
|
|
|
464
|
-
###
|
|
465
|
-
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
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
|
-
|
|
412
|
+
return results.flat();
|
|
413
|
+
}
|
|
475
414
|
```
|
|
476
415
|
|
|
477
|
-
|
|
478
|
-
Extension of the static method _all_ with cancellation support.
|
|
416
|
+
---
|
|
479
417
|
|
|
480
|
-
|
|
481
|
-
```javascript
|
|
482
|
-
const controller = new AbortController();
|
|
418
|
+
## π¨ Design Philosophy
|
|
483
419
|
|
|
484
|
-
|
|
420
|
+
### 1. Progressive Enhancement
|
|
485
421
|
|
|
486
|
-
|
|
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
|
-
|
|
424
|
+
```typescript
|
|
425
|
+
// Simple
|
|
426
|
+
const data = await Futurable.fetch('/api/data')
|
|
427
|
+
.then(res => res.json());
|
|
493
428
|
|
|
494
|
-
|
|
429
|
+
// Add cancellation
|
|
430
|
+
const request = Futurable.fetch('/api/data')
|
|
431
|
+
.then(res => res.json());
|
|
432
|
+
request.cancel();
|
|
495
433
|
|
|
496
|
-
//
|
|
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
|
-
|
|
499
|
-
1,
|
|
500
|
-
Futurable.resolve(true),
|
|
501
|
-
new Futurable/*...*/
|
|
502
|
-
]
|
|
442
|
+
### 2. Type Safety First
|
|
503
443
|
|
|
504
|
-
|
|
444
|
+
Full TypeScript support with inference:
|
|
505
445
|
|
|
506
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
456
|
+
No external dependencies. Small bundle size. Tree-shakeable.
|
|
517
457
|
|
|
518
|
-
|
|
519
|
-
1,
|
|
520
|
-
Futurable.resolve(true, controller.signal),
|
|
521
|
-
new Futurable/*...*/
|
|
522
|
-
], controller.signal);
|
|
458
|
+
### 4. Promise Compatible
|
|
523
459
|
|
|
524
|
-
|
|
460
|
+
`Futurable` **is** a Promise. Works with `async/await`, `Promise.all()`, and any Promise-based API.
|
|
525
461
|
|
|
526
|
-
|
|
462
|
+
---
|
|
527
463
|
|
|
528
|
-
|
|
464
|
+
## π¦ Installation
|
|
529
465
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
new Futurable/*...*/
|
|
534
|
-
];
|
|
466
|
+
```bash
|
|
467
|
+
npm install @ndriadev/futurable
|
|
468
|
+
```
|
|
535
469
|
|
|
536
|
-
|
|
470
|
+
```bash
|
|
471
|
+
yarn add @ndriadev/futurable
|
|
472
|
+
```
|
|
537
473
|
|
|
538
|
-
|
|
474
|
+
```bash
|
|
475
|
+
pnpm add @ndriadev/futurable
|
|
539
476
|
```
|
|
540
477
|
|
|
541
|
-
|
|
542
|
-
Extension of the static method _any_ with cancellation support.
|
|
478
|
+
---
|
|
543
479
|
|
|
544
|
-
|
|
545
|
-
```javascript
|
|
546
|
-
const controller = new AbortController();
|
|
547
|
-
//...code
|
|
480
|
+
## π― Quick Start
|
|
548
481
|
|
|
549
|
-
Futurable
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
], controller.signal);
|
|
482
|
+
### Basic Futurable
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
import { Futurable } from '@ndriadev/futurable';
|
|
554
486
|
|
|
555
|
-
|
|
487
|
+
// Cancellable fetch
|
|
488
|
+
const request = Futurable.fetch('/api/data')
|
|
489
|
+
.then(res => res.json());
|
|
556
490
|
|
|
557
|
-
|
|
491
|
+
request.cancel(); // Cancel if needed
|
|
492
|
+
```
|
|
558
493
|
|
|
559
|
-
|
|
494
|
+
### Basic FuturableTask
|
|
560
495
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
Futurable.resolve(true, controller.signal),
|
|
564
|
-
new Futurable/*...*/
|
|
565
|
-
];
|
|
496
|
+
```typescript
|
|
497
|
+
import { FuturableTask } from '@ndriadev/futurable';
|
|
566
498
|
|
|
567
|
-
|
|
499
|
+
// Define work
|
|
500
|
+
const task = FuturableTask
|
|
501
|
+
.of(() => fetch('/api/data'))
|
|
502
|
+
.map(res => res.json())
|
|
503
|
+
.retry(3);
|
|
568
504
|
|
|
569
|
-
|
|
505
|
+
// Execute when ready
|
|
506
|
+
const data = await task.run();
|
|
570
507
|
```
|
|
571
508
|
|
|
572
|
-
|
|
573
|
-
Extension of the static method _race_ with cancellation support.
|
|
509
|
+
---
|
|
574
510
|
|
|
575
|
-
|
|
576
|
-
```javascript
|
|
577
|
-
const controller = new AbortController();
|
|
578
|
-
//...code
|
|
511
|
+
## π Documentation
|
|
579
512
|
|
|
580
|
-
|
|
581
|
-
1,
|
|
582
|
-
Futurable.resolve(true, controller.signal),
|
|
583
|
-
new Futurable/*...*/
|
|
584
|
-
], controller.signal);
|
|
513
|
+
π **[Complete Documentation](https://futurable.ndria.dev/)**
|
|
585
514
|
|
|
586
|
-
|
|
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
|
-
|
|
520
|
+
---
|
|
589
521
|
|
|
590
|
-
|
|
522
|
+
## π Key Features
|
|
591
523
|
|
|
592
|
-
|
|
593
|
-
1,
|
|
594
|
-
Futurable.resolve(true, controller.signal),
|
|
595
|
-
new Futurable/*...*/
|
|
596
|
-
];
|
|
524
|
+
### Futurable
|
|
597
525
|
|
|
598
|
-
|
|
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
|
-
|
|
601
|
-
```
|
|
536
|
+
### FuturableTask
|
|
602
537
|
|
|
603
|
-
|
|
604
|
-
|
|
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
|
-
|
|
607
|
-
```javascript
|
|
608
|
-
//...code
|
|
609
|
-
const polling = Futurable.polling(() => Futurable.fetch(/*...*/)), {interval: 1000});
|
|
610
|
-
polling.catch(err => console.error(err));
|
|
551
|
+
---
|
|
611
552
|
|
|
612
|
-
|
|
553
|
+
## π― Use Cases
|
|
613
554
|
|
|
614
|
-
|
|
615
|
-
```
|
|
555
|
+
### Perfect For
|
|
616
556
|
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
621
|
-
```javascript
|
|
622
|
-
//...code
|
|
623
|
-
const {promise, resolve, reject} = Futurable.withResolvers();
|
|
566
|
+
---
|
|
624
567
|
|
|
625
|
-
|
|
568
|
+
## π Browser & Node.js Support
|
|
626
569
|
|
|
627
|
-
|
|
570
|
+
- β
All modern browsers (Chrome, Firefox, Safari, Edge)
|
|
571
|
+
- β
Node.js 14+
|
|
572
|
+
- β
TypeScript 4.5+
|
|
573
|
+
- β
ES2015+ (ES6+)
|
|
628
574
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## π License
|
|
632
578
|
|
|
579
|
+
[MIT](LICENSE) Β© [nDriaDev](https://github.com/nDriaDev)
|
|
633
580
|
|
|
634
|
-
|
|
635
|
-
- Extends fetch api support to third library like axios.
|
|
636
|
-
- Implement promise cache.
|
|
581
|
+
---
|
|
637
582
|
|
|
583
|
+
## π Acknowledgments
|
|
638
584
|
|
|
639
|
-
|
|
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
|
-
|
|
606
|
+
</div>
|