@enegalan/request-manager 1.0.3 → 1.1.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 +313 -220
- package/dist/request-manager.cjs.js +299 -382
- package/dist/request-manager.cjs.js.map +1 -1
- package/dist/request-manager.cjs.min.js +3 -6
- package/dist/request-manager.cjs.min.js.map +1 -1
- package/dist/request-manager.esm.js +299 -382
- package/dist/request-manager.esm.js.map +1 -1
- package/dist/request-manager.esm.min.js +3 -6
- package/dist/request-manager.esm.min.js.map +1 -1
- package/dist/request-manager.js +299 -382
- package/dist/request-manager.js.map +1 -1
- package/dist/request-manager.min.js +2 -5
- package/dist/request-manager.min.js.map +1 -1
- package/dist/request-manager.umd.js +299 -382
- package/dist/request-manager.umd.js.map +1 -1
- package/dist/request-manager.umd.min.js +2 -5
- package/dist/request-manager.umd.min.js.map +1 -1
- package/index.d.ts +118 -74
- package/main.js +299 -382
- package/package.json +78 -69
package/README.md
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@enegalan/request-manager)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
RequestManager is a JavaScript library designed to manage and regulate HTTP requests efficiently. It
|
|
6
|
+
RequestManager is a JavaScript library designed to manage and regulate HTTP requests efficiently. It cancels duplicate in-flight calls and works with fetch, axios, jQuery.ajax, Ext.Ajax, raw XHR, and custom clients.
|
|
7
7
|
|
|
8
8
|
## Key Features
|
|
9
9
|
|
|
10
|
-
- **Universal Compatibility**:
|
|
11
|
-
- **Automatic Cancellation**: When a request is repeated with the same
|
|
12
|
-
- **Prioritizes Recent Requests**:
|
|
13
|
-
- **Simple API**:
|
|
14
|
-
- **Adapt to your requirements**:
|
|
10
|
+
- **Universal Compatibility**: Dedicated helpers for fetch, axios, ajax-style clients (jQuery / Ext.Ajax), and XMLHttpRequest — plus a low-level `request()` escape hatch
|
|
11
|
+
- **Automatic Cancellation**: When a request is repeated with the same identifier, the previous request is automatically cancelled. The ID comes from the cleaned URL, or from `options.requestKey`
|
|
12
|
+
- **Prioritizes Recent Requests**: Only the most recent request for a given ID is kept; older ones are aborted
|
|
13
|
+
- **Simple API**: Prefer the helper that matches your HTTP client; wire cancel yourself only with `request()`
|
|
14
|
+
- **Adapt to your requirements**: Shared options (`requestKey`, `noCancel`, `includeQuery`, ...) across helpers
|
|
15
15
|
- **TypeScript Support**: Full TypeScript type definitions included
|
|
16
16
|
- **Multiple Module Formats**: ESM, CommonJS, and UMD builds available
|
|
17
17
|
|
|
@@ -24,16 +24,19 @@ npm install @enegalan/request-manager
|
|
|
24
24
|
### Usage in Different Environments
|
|
25
25
|
|
|
26
26
|
**ES Modules (recommended):**
|
|
27
|
+
|
|
27
28
|
```javascript
|
|
28
29
|
import RequestManager from '@enegalan/request-manager';
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
**CommonJS:**
|
|
33
|
+
|
|
32
34
|
```javascript
|
|
33
35
|
const { RequestManager } = require('@enegalan/request-manager');
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
**Browser (CDN):**
|
|
39
|
+
|
|
37
40
|
```html
|
|
38
41
|
<!-- Using unpkg -->
|
|
39
42
|
<script src="https://unpkg.com/@enegalan/request-manager/dist/request-manager.min.js"></script>
|
|
@@ -42,7 +45,7 @@ const { RequestManager } = require('@enegalan/request-manager');
|
|
|
42
45
|
<script src="https://cdn.jsdelivr.net/npm/@enegalan/request-manager/dist/request-manager.min.js"></script>
|
|
43
46
|
|
|
44
47
|
<script>
|
|
45
|
-
|
|
48
|
+
const requestManager = new RequestManager();
|
|
46
49
|
</script>
|
|
47
50
|
```
|
|
48
51
|
|
|
@@ -61,66 +64,98 @@ const xhrResult: XhrResponse<{ name: string }> = await requestManager.xhr('/api/
|
|
|
61
64
|
|
|
62
65
|
## Usage
|
|
63
66
|
|
|
64
|
-
###
|
|
67
|
+
### Which method should I use?
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
import RequestManager from '@enegalan/request-manager';
|
|
69
|
+
Pick the **dedicated helper** for your HTTP client. Use `request()` only when none of the helpers fit.
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
| Client | Use this | Why |
|
|
72
|
+
| --------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
73
|
+
| `fetch` | **`fetch(url, options)`** | Creates the AbortSignal and passes it to `fetch` for you |
|
|
74
|
+
| `axios` | **`axios(url, options, axiosInstance?)`** | Creates an `AbortSignal` and wires cancel for you (axios ≥ 0.22) |
|
|
75
|
+
| jQuery `.ajax`, Ext.Ajax, similar | **`ajax(ajaxFunction, url, options)`** | Runs your ajax function, then wires abort for you (`req.abort`, `Ext.Ajax.abort(req)`, or `xhr.abort`) |
|
|
76
|
+
| Raw `XMLHttpRequest` | **`xhr(url, options)`** | Owns open/send and abort lifecycle |
|
|
77
|
+
| Custom / already-started Promise | **`request(url, promiseOrFn, options)`** | Escape hatch — **you** must pass `signal` / `cancelToken` / `addAbortListener` |
|
|
78
|
+
|
|
79
|
+
**Rule of thumb**
|
|
80
|
+
|
|
81
|
+
1. Known client → use its helper (`fetch` / `axios` / `ajax` / `xhr`).
|
|
82
|
+
2. Helper already cancels the real network call — no manual abort wiring.
|
|
83
|
+
3. `request()` is for edge cases (wrapping an existing Promise, exotic clients). Same cancellation _map_ as the helpers, but abort plumbing is your job.
|
|
70
84
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
```javascript
|
|
86
|
+
// Preferred
|
|
87
|
+
requestManager.fetch('/api/users');
|
|
88
|
+
requestManager.axios('/api/users');
|
|
89
|
+
requestManager.ajax(({ url, ...opts }) => $.ajax({ url, ...opts }), '/api/users');
|
|
90
|
+
requestManager.ajax(({ url, ...opts }) => Ext.Ajax.request({ url, ...opts }), '/api/users');
|
|
91
|
+
requestManager.xhr('/api/users');
|
|
92
|
+
|
|
93
|
+
// Escape hatch — you wire cancel yourself (see sandbox / API notes below)
|
|
94
|
+
requestManager.request('/api/users', ({ options }) => fetch('/api/users', { signal: options.signal, ...options }));
|
|
81
95
|
```
|
|
82
96
|
|
|
83
|
-
|
|
97
|
+
> [!IMPORTANT]
|
|
98
|
+
> Calling `request(url, Ext.Ajax.request(...))` or `request(url, $.ajax(...))` **without** linking abort (via `addAbortListener` / `cancelToken` / `signal`) does **not** abort the browser request when a duplicate starts. Use **`ajax()`** for those clients.
|
|
99
|
+
|
|
100
|
+
### Basic Example with fetch()
|
|
84
101
|
|
|
85
102
|
```javascript
|
|
86
103
|
import RequestManager from '@enegalan/request-manager';
|
|
87
104
|
|
|
88
|
-
const requestManager = new RequestManager();
|
|
105
|
+
const requestManager = new RequestManager({ verbose: true });
|
|
89
106
|
|
|
90
|
-
requestManager
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.
|
|
96
|
-
.
|
|
107
|
+
requestManager
|
|
108
|
+
.fetch('/api/users')
|
|
109
|
+
.then((response) => response.json())
|
|
110
|
+
.then((data) => console.log(data))
|
|
111
|
+
.catch((error) => {
|
|
112
|
+
if (error.message.includes('was cancelled')) {
|
|
113
|
+
console.log('Request was cancelled');
|
|
114
|
+
} else {
|
|
115
|
+
console.error('Request failed:', error);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
97
118
|
```
|
|
98
119
|
|
|
99
|
-
###
|
|
120
|
+
### POST Request with Options
|
|
100
121
|
|
|
101
122
|
```javascript
|
|
102
123
|
import RequestManager from '@enegalan/request-manager';
|
|
103
124
|
|
|
104
125
|
const requestManager = new RequestManager();
|
|
105
126
|
|
|
106
|
-
requestManager
|
|
107
|
-
|
|
108
|
-
|
|
127
|
+
requestManager
|
|
128
|
+
.fetch('/api/users', {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: { 'Content-Type': 'application/json' },
|
|
131
|
+
body: JSON.stringify({ name: 'John' }),
|
|
132
|
+
})
|
|
133
|
+
.then((response) => response.json())
|
|
134
|
+
.then((data) => console.log(data));
|
|
109
135
|
```
|
|
110
136
|
|
|
111
|
-
### Using request()
|
|
137
|
+
### Using request()
|
|
138
|
+
|
|
139
|
+
`request()` is the low-level API when you already have a Promise or need custom wiring.
|
|
112
140
|
|
|
113
141
|
```javascript
|
|
114
142
|
import RequestManager from '@enegalan/request-manager';
|
|
115
143
|
|
|
116
144
|
const requestManager = new RequestManager();
|
|
117
145
|
|
|
118
|
-
//
|
|
119
|
-
requestManager
|
|
120
|
-
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
.then(
|
|
146
|
+
// Function form — options include signal; pass it into fetch (or prefer requestManager.fetch)
|
|
147
|
+
requestManager
|
|
148
|
+
.request('/api/users', ({ options }) => {
|
|
149
|
+
return fetch('/api/users', { signal: options.signal, ...options });
|
|
150
|
+
})
|
|
151
|
+
.then((response) => response.json())
|
|
152
|
+
.then((data) => console.log(data));
|
|
153
|
+
|
|
154
|
+
// Pre-created Promise — must also pass abortController / cancelToken or cancel is incomplete
|
|
155
|
+
const abortController = requestManager.getAbortController();
|
|
156
|
+
requestManager.request('/api/users', fetch('/api/users', { signal: abortController.signal }), {
|
|
157
|
+
abortController,
|
|
158
|
+
});
|
|
124
159
|
```
|
|
125
160
|
|
|
126
161
|
### Automatic Cancellation with Same URL
|
|
@@ -132,17 +167,17 @@ const requestManager = new RequestManager();
|
|
|
132
167
|
|
|
133
168
|
// By default, requests with the same URL (cleaned) will cancel previous ones
|
|
134
169
|
// The URL is automatically cleaned (protocol and query params removed) to generate the request ID
|
|
135
|
-
requestManager.fetch('/api/search?q=test')
|
|
136
|
-
.catch(error => {
|
|
170
|
+
requestManager.fetch('/api/search?q=test').catch((error) => {
|
|
137
171
|
console.log('First request cancelled:', error.message);
|
|
138
|
-
|
|
172
|
+
});
|
|
139
173
|
|
|
140
174
|
// This second request will automatically cancel the first one
|
|
141
175
|
// because they share the same cleaned URL
|
|
142
176
|
setTimeout(() => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
177
|
+
requestManager
|
|
178
|
+
.fetch('/api/search?q=updated')
|
|
179
|
+
.then((response) => response.json())
|
|
180
|
+
.then((data) => console.log('Second request completed:', data));
|
|
146
181
|
}, 100);
|
|
147
182
|
```
|
|
148
183
|
|
|
@@ -154,20 +189,22 @@ import RequestManager from '@enegalan/request-manager';
|
|
|
154
189
|
const requestManager = new RequestManager();
|
|
155
190
|
|
|
156
191
|
// You can use requestKey to override the default URL-based ID generation
|
|
157
|
-
requestManager
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
192
|
+
requestManager
|
|
193
|
+
.fetch('/api/search?q=test', {
|
|
194
|
+
requestKey: 'search-users', // Custom key instead of cleaned URL
|
|
195
|
+
})
|
|
196
|
+
.catch((error) => {
|
|
197
|
+
console.log('First request cancelled:', error.message);
|
|
198
|
+
});
|
|
163
199
|
|
|
164
200
|
// This second request will cancel the first one because they share the same requestKey
|
|
165
201
|
setTimeout(() => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
202
|
+
requestManager
|
|
203
|
+
.fetch('/api/search?q=updated', {
|
|
204
|
+
requestKey: 'search-users', // Same key = same request ID = cancellation
|
|
205
|
+
})
|
|
206
|
+
.then((response) => response.json())
|
|
207
|
+
.then((data) => console.log('Second request completed:', data));
|
|
171
208
|
}, 100);
|
|
172
209
|
```
|
|
173
210
|
|
|
@@ -180,9 +217,9 @@ const requestManager = new RequestManager();
|
|
|
180
217
|
|
|
181
218
|
// You can use a function to generate the requestKey dynamically
|
|
182
219
|
function searchUsers(query) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
220
|
+
return requestManager.fetch(`/api/search?q=${query}`, {
|
|
221
|
+
requestKey: () => `search-${query}`, // Function that returns the key
|
|
222
|
+
});
|
|
186
223
|
}
|
|
187
224
|
|
|
188
225
|
// Both calls will share the same requestKey and cancel each other
|
|
@@ -199,49 +236,46 @@ const requestManager = new RequestManager();
|
|
|
199
236
|
|
|
200
237
|
// Use noCancel: true to allow multiple requests to execute concurrently
|
|
201
238
|
// This is useful for lazy loading scenarios where you want all requests to complete
|
|
202
|
-
requestManager
|
|
203
|
-
|
|
204
|
-
|
|
239
|
+
requestManager
|
|
240
|
+
.fetch('/api/lazy?load=1', { noCancel: true })
|
|
241
|
+
.then((response) => response.json())
|
|
242
|
+
.then((data) => console.log('Load 1:', data));
|
|
205
243
|
|
|
206
|
-
requestManager
|
|
207
|
-
|
|
208
|
-
|
|
244
|
+
requestManager
|
|
245
|
+
.fetch('/api/lazy?load=2', { noCancel: true })
|
|
246
|
+
.then((response) => response.json())
|
|
247
|
+
.then((data) => console.log('Load 2:', data));
|
|
209
248
|
|
|
210
|
-
requestManager
|
|
211
|
-
|
|
212
|
-
|
|
249
|
+
requestManager
|
|
250
|
+
.fetch('/api/lazy?load=3', { noCancel: true })
|
|
251
|
+
.then((response) => response.json())
|
|
252
|
+
.then((data) => console.log('Load 3:', data));
|
|
213
253
|
|
|
214
254
|
// All three requests will execute concurrently without canceling each other
|
|
215
255
|
// Even though they share the same cleaned URL (without query params)
|
|
216
256
|
```
|
|
217
257
|
|
|
218
|
-
### Using
|
|
258
|
+
### Using includeQuery to Distinguish Query Strings
|
|
219
259
|
|
|
220
260
|
```javascript
|
|
221
|
-
import axios from 'axios';
|
|
222
261
|
import RequestManager from '@enegalan/request-manager';
|
|
223
262
|
|
|
224
263
|
const requestManager = new RequestManager();
|
|
225
264
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
.
|
|
236
|
-
|
|
237
|
-
console.log('Request was cancelled');
|
|
238
|
-
} else {
|
|
239
|
-
console.error('Request failed:', error);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
265
|
+
// By default, query params are stripped from the ID:
|
|
266
|
+
// /api/users?page=1 and /api/users?page=2 share the same ID and cancel each other.
|
|
267
|
+
|
|
268
|
+
// With includeQuery: true, the query string is part of the ID
|
|
269
|
+
requestManager.fetch('/api/users?page=1', { includeQuery: true });
|
|
270
|
+
requestManager.fetch('/api/users?page=2', { includeQuery: true });
|
|
271
|
+
// Both run — different query = different ID
|
|
272
|
+
|
|
273
|
+
// Same full URL still cancels the previous one
|
|
274
|
+
requestManager.fetch('/api/users?page=1', { includeQuery: true });
|
|
275
|
+
requestManager.fetch('/api/users?page=1', { includeQuery: true }); // cancels the previous page=1
|
|
242
276
|
```
|
|
243
277
|
|
|
244
|
-
### Using with Axios
|
|
278
|
+
### Using with Axios
|
|
245
279
|
|
|
246
280
|
```javascript
|
|
247
281
|
import axios from 'axios';
|
|
@@ -249,64 +283,73 @@ import RequestManager from '@enegalan/request-manager';
|
|
|
249
283
|
|
|
250
284
|
const requestManager = new RequestManager();
|
|
251
285
|
|
|
252
|
-
requestManager
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
console.error('Request failed:', error);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
286
|
+
requestManager
|
|
287
|
+
.axios('/api/users')
|
|
288
|
+
.then((response) => console.log(response.data))
|
|
289
|
+
.catch((error) => {
|
|
290
|
+
if (axios.isCancel(error)) {
|
|
291
|
+
console.log('Request was cancelled');
|
|
292
|
+
} else {
|
|
293
|
+
console.error('Request failed:', error);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
265
296
|
```
|
|
266
297
|
|
|
267
|
-
### Using with
|
|
298
|
+
### Using with jQuery / Ext.Ajax (`ajax()`)
|
|
299
|
+
|
|
300
|
+
`ajax()` invokes your function, inspects the returned request object, and registers abort automatically (`req.abort`, `Ext.Ajax.abort(req)`, or `xhr.abort`).
|
|
268
301
|
|
|
269
302
|
```javascript
|
|
270
303
|
import RequestManager from '@enegalan/request-manager';
|
|
271
304
|
|
|
272
305
|
const requestManager = new RequestManager();
|
|
273
306
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
})
|
|
289
|
-
.
|
|
290
|
-
|
|
307
|
+
// jQuery
|
|
308
|
+
requestManager.ajax($.ajax.bind($), '/api/users', { method: 'GET' });
|
|
309
|
+
|
|
310
|
+
// Ext.Ajax — return the Ext request object (not a Promise)
|
|
311
|
+
requestManager.ajax(({ url, ...options }) => Ext.Ajax.request({ url, ...options }), '/api/users');
|
|
312
|
+
|
|
313
|
+
// Or bind Ext.Ajax.request directly when options shape matches
|
|
314
|
+
requestManager.ajax(Ext.Ajax.request.bind(Ext.Ajax), '/api/users');
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Equivalent with `request()` (more boilerplate — not recommended):
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
requestManager.request('/api/users', ({ options }) => {
|
|
321
|
+
const req = Ext.Ajax.request({ url: '/api/users', ...options });
|
|
322
|
+
requestManager.addAbortListener(() => Ext.Ajax.abort(req), options.signal);
|
|
323
|
+
return req;
|
|
324
|
+
});
|
|
291
325
|
```
|
|
292
326
|
|
|
293
|
-
### Using with
|
|
327
|
+
### Using with Other Libraries
|
|
328
|
+
|
|
329
|
+
If there is no dedicated helper, use `request()` and **must** abort on `options.signal` (or pass `cancelToken` / `addAbortListener`).
|
|
294
330
|
|
|
295
331
|
```javascript
|
|
296
332
|
import RequestManager from '@enegalan/request-manager';
|
|
297
333
|
|
|
298
334
|
const requestManager = new RequestManager();
|
|
299
335
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
.
|
|
336
|
+
requestManager
|
|
337
|
+
.request('/api/data', ({ options }) => {
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
const xhr = new XMLHttpRequest();
|
|
340
|
+
xhr.open('GET', '/api/data');
|
|
341
|
+
xhr.onload = () => resolve(xhr.responseText);
|
|
342
|
+
xhr.onerror = () => reject(new Error('Request failed'));
|
|
343
|
+
xhr.send();
|
|
344
|
+
|
|
345
|
+
options.signal.addEventListener('abort', () => {
|
|
346
|
+
xhr.abort();
|
|
347
|
+
reject(new Error('Request was cancelled'));
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
})
|
|
351
|
+
.then((data) => console.log(data))
|
|
352
|
+
.catch((error) => console.error(error));
|
|
310
353
|
```
|
|
311
354
|
|
|
312
355
|
## API Reference
|
|
@@ -316,10 +359,12 @@ requestManager.request('/api/data', existingPromise, {
|
|
|
316
359
|
Creates a new RequestManager instance.
|
|
317
360
|
|
|
318
361
|
**Parameters:**
|
|
362
|
+
|
|
319
363
|
- `options` (Object, optional): Configuration options
|
|
320
|
-
|
|
364
|
+
- `verbose` (boolean, optional): If true, cancellation rejects with a message that includes the request id. If false (default), cancellation is silent (wrapper promise does not settle; nothing is logged).
|
|
321
365
|
|
|
322
366
|
**Example:**
|
|
367
|
+
|
|
323
368
|
```javascript
|
|
324
369
|
// Create with verbose mode enabled
|
|
325
370
|
const requestManager = new RequestManager({ verbose: true });
|
|
@@ -327,36 +372,40 @@ const requestManager = new RequestManager({ verbose: true });
|
|
|
327
372
|
|
|
328
373
|
### `request(url, requestPromise, options)`
|
|
329
374
|
|
|
330
|
-
|
|
375
|
+
Low-level entry point. Tracks the call by ID and cancels the previous one with the same ID — but **you** must connect abort to the underlying client (`signal`, `cancelToken`, or `addAbortListener`). Otherwise the manager drops the tracked entry while the HTTP request may keep running.
|
|
331
376
|
|
|
332
377
|
**Parameters:**
|
|
378
|
+
|
|
333
379
|
- `url` (string): The URL of the request (used to generate request ID from cleaned URL)
|
|
334
|
-
- `requestPromise` (Promise|Function|string):
|
|
380
|
+
- `requestPromise` (Promise|Function|string): A Promise from any HTTP library, a Function that receives `{ options }` and returns a Promise/request object, or a URL string (fetch internally)
|
|
335
381
|
- `options` (Object, optional): Configuration options
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
382
|
+
- `abortController` (AbortController): AbortController instance (created automatically if not provided)
|
|
383
|
+
- `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
|
|
384
|
+
- `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will share the same ID and cancel previous ones. If not provided, the cleaned URL is used as the key. Can be a string, number, or function that returns a key.
|
|
385
|
+
- `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests. Useful for lazy loading scenarios where multiple requests should execute in parallel.
|
|
386
|
+
- `includeQuery` (boolean): If true, keeps the query string when generating the request ID from the URL.
|
|
340
387
|
|
|
341
388
|
> [!TIP]
|
|
342
389
|
> When `requestPromise` is a Function, you can pass custom properties in `options`. These will be accessible inside the callback via the `{ options }` parameter.
|
|
343
390
|
|
|
344
391
|
**Returns:** Promise that resolves/rejects based on the most recent request
|
|
345
392
|
|
|
346
|
-
**Note:** The request ID is automatically generated from the cleaned URL (protocol and query params removed) unless `requestKey` is specified. When `noCancel` is true, a unique ID is generated for each request to prevent cancellation. When `requestPromise` is a Function, it receives `{ options }` where `options` contains the `signal` (AbortSignal) and any other fetch options.
|
|
393
|
+
**Note:** The request ID is automatically generated from the cleaned URL (protocol and hash removed; query params removed unless `includeQuery` is true) unless `requestKey` is specified. When `noCancel` is true, a unique ID is generated for each request to prevent cancellation. When `requestPromise` is a Function, it receives `{ options }` where `options` contains the `signal` (AbortSignal) and any other fetch options.
|
|
347
394
|
|
|
348
395
|
### `fetch(url, options)`
|
|
349
396
|
|
|
350
397
|
Executes an HTTP request using fetch, cancelling any previous request with the same identifier.
|
|
351
398
|
|
|
352
399
|
**Parameters:**
|
|
400
|
+
|
|
353
401
|
- `url` (string): The URL to fetch
|
|
354
402
|
- `options` (Object, optional): Configuration options (same as `request()` method)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
403
|
+
- `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If not provided, the cleaned URL is used as the key.
|
|
404
|
+
- `abortController` (AbortController): AbortController instance (created automatically if not provided)
|
|
405
|
+
- `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
|
|
406
|
+
- `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
|
|
407
|
+
- `includeQuery` (boolean): If true, keeps the query string in the URL-based request ID
|
|
408
|
+
- Any other properties are passed as fetch options (method, headers, body, etc.)
|
|
360
409
|
|
|
361
410
|
**Returns:** Promise that resolves/rejects based on the most recent request
|
|
362
411
|
|
|
@@ -367,18 +416,21 @@ Executes an HTTP request using fetch, cancelling any previous request with the s
|
|
|
367
416
|
Executes an HTTP request using axios, cancelling any previous request with the same identifier.
|
|
368
417
|
|
|
369
418
|
**Parameters:**
|
|
419
|
+
|
|
370
420
|
- `url` (string): The URL to request
|
|
371
421
|
- `options` (Object, optional): Configuration options
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
422
|
+
- `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
|
|
423
|
+
- `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
|
|
424
|
+
- `includeQuery` (boolean): If true, keeps the query string in the URL-based request ID
|
|
425
|
+
- Any other properties are passed as axios options (method, headers, params, data, etc.)
|
|
375
426
|
- `axiosInstance` (Object, optional): Custom axios instance to use. If not provided, uses the global `axios` object.
|
|
376
427
|
|
|
377
428
|
**Returns:** Promise that resolves/rejects based on the most recent request
|
|
378
429
|
|
|
379
|
-
**Note:** This method automatically creates
|
|
430
|
+
**Note:** This method automatically creates an AbortController and passes its `signal` in the axios config, so cancellation requires axios ≥ 0.22. The request ID is automatically generated from the cleaned URL unless `requestKey` is specified. When `noCancel` is true, a unique ID is generated for each request.
|
|
380
431
|
|
|
381
432
|
**Example:**
|
|
433
|
+
|
|
382
434
|
```javascript
|
|
383
435
|
import axios from 'axios';
|
|
384
436
|
import RequestManager from '@enegalan/request-manager';
|
|
@@ -386,71 +438,69 @@ import RequestManager from '@enegalan/request-manager';
|
|
|
386
438
|
const requestManager = new RequestManager();
|
|
387
439
|
|
|
388
440
|
// Simple GET request (uses global axios)
|
|
389
|
-
requestManager
|
|
390
|
-
|
|
391
|
-
|
|
441
|
+
requestManager
|
|
442
|
+
.axios('/api/users')
|
|
443
|
+
.then((response) => console.log(response.data))
|
|
444
|
+
.catch((error) => console.error(error));
|
|
392
445
|
|
|
393
446
|
// With custom axios instance
|
|
394
447
|
const apiClient = axios.create({
|
|
395
|
-
|
|
396
|
-
|
|
448
|
+
baseURL: 'https://api.example.com',
|
|
449
|
+
timeout: 5000,
|
|
397
450
|
});
|
|
398
451
|
|
|
399
|
-
requestManager.axios('/users', {}, apiClient)
|
|
400
|
-
.then(response => console.log(response.data));
|
|
452
|
+
requestManager.axios('/users', {}, apiClient).then((response) => console.log(response.data));
|
|
401
453
|
|
|
402
454
|
// POST request with options
|
|
403
|
-
requestManager
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
455
|
+
requestManager
|
|
456
|
+
.axios('/api/users', {
|
|
457
|
+
method: 'POST',
|
|
458
|
+
data: { name: 'John' },
|
|
459
|
+
headers: { 'Content-Type': 'application/json' },
|
|
460
|
+
})
|
|
461
|
+
.then((response) => console.log(response.data));
|
|
409
462
|
```
|
|
410
463
|
|
|
411
464
|
### `ajax(ajaxFunction, url, options)`
|
|
412
465
|
|
|
413
|
-
|
|
466
|
+
Helper for **ajax-style clients** (jQuery.ajax, Ext.Ajax, etc.) that return a request object rather than (or in addition to) a Promise.
|
|
467
|
+
|
|
468
|
+
Calls `ajaxFunction({ url, ...options })`, then auto-wires cancel by inspecting the returned object:
|
|
469
|
+
|
|
470
|
+
1. `req.abort` if present (jQuery)
|
|
471
|
+
2. else `Ext.Ajax.abort(req)` when Ext is available and `req.xhr` exists
|
|
472
|
+
3. else `req.xhr.abort` / raw `XMLHttpRequest.abort`
|
|
414
473
|
|
|
415
474
|
**Parameters:**
|
|
416
|
-
|
|
475
|
+
|
|
476
|
+
- `ajaxFunction` (Function): Receives `{ url, ...options }` and returns the library request object (or a Promise).
|
|
417
477
|
- `url` (string): The URL to request
|
|
418
478
|
- `options` (Object, optional): Configuration options
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
479
|
+
- `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
|
|
480
|
+
- `abortController` (AbortController): AbortController instance (created automatically if not provided)
|
|
481
|
+
- `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
|
|
482
|
+
- `verbose` (boolean): If true, cancellation rejects with a message that includes the request id
|
|
483
|
+
- `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
|
|
484
|
+
- `includeQuery` (boolean): If true, keeps the query string in the URL-based request ID
|
|
485
|
+
- Any other properties are passed to the ajax method function
|
|
425
486
|
|
|
426
487
|
**Returns:** Promise that resolves/rejects based on the most recent request
|
|
427
488
|
|
|
428
489
|
**Example:**
|
|
490
|
+
|
|
429
491
|
```javascript
|
|
430
492
|
import RequestManager from '@enegalan/request-manager';
|
|
431
493
|
|
|
432
494
|
const requestManager = new RequestManager();
|
|
433
495
|
|
|
434
|
-
//
|
|
435
|
-
requestManager
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
error: reject
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
},
|
|
446
|
-
'/api/users',
|
|
447
|
-
{
|
|
448
|
-
method: 'GET',
|
|
449
|
-
headers: { 'Content-Type': 'application/json' }
|
|
450
|
-
}
|
|
451
|
-
)
|
|
452
|
-
.then(data => console.log(data))
|
|
453
|
-
.catch(error => console.error(error));
|
|
496
|
+
// jQuery
|
|
497
|
+
requestManager
|
|
498
|
+
.ajax($.ajax.bind($), '/api/users', { method: 'GET' })
|
|
499
|
+
.then((data) => console.log(data))
|
|
500
|
+
.catch((error) => console.error(error));
|
|
501
|
+
|
|
502
|
+
// Ext.Ajax
|
|
503
|
+
requestManager.ajax(({ url, ...options }) => Ext.Ajax.request({ url, ...options }), '/api/users');
|
|
454
504
|
```
|
|
455
505
|
|
|
456
506
|
### `xhr(url, options)`
|
|
@@ -458,45 +508,80 @@ requestManager.ajax(
|
|
|
458
508
|
Executes an HTTP request using XMLHttpRequest, cancelling any previous request with the same identifier.
|
|
459
509
|
|
|
460
510
|
**Parameters:**
|
|
511
|
+
|
|
461
512
|
- `url` (string): The URL to request
|
|
462
513
|
- `options` (Object, optional): Configuration options
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
514
|
+
- `method` (string): HTTP method (GET, POST, PUT, DELETE, etc.). Defaults to 'GET'.
|
|
515
|
+
- `headers` (Object): Headers object to set on the request
|
|
516
|
+
- `body` (string|FormData|Blob|ArrayBuffer): Request body
|
|
517
|
+
- `responseType` (string): Response type ('text', 'json', 'blob', 'arraybuffer', 'document'). Defaults to 'text'.
|
|
518
|
+
- `withCredentials` (boolean): Whether to send credentials with the request
|
|
519
|
+
- `timeout` (number): Request timeout in milliseconds
|
|
520
|
+
- `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If provided, requests with the same key will cancel previous ones. Can be a string, number, or function that returns a key.
|
|
521
|
+
- `abortController` (AbortController): AbortController instance (created automatically if not provided)
|
|
522
|
+
- `verbose` (boolean): If true, cancellation rejects with a message that includes the request id
|
|
523
|
+
- `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
|
|
524
|
+
- `includeQuery` (boolean): If true, keeps the query string in the URL-based request ID
|
|
473
525
|
|
|
474
526
|
**Returns:** Promise that resolves/rejects based on the most recent request. The resolved value is an object with:
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
527
|
+
|
|
528
|
+
- `data`: The response data (automatically parsed as JSON if Content-Type is application/json)
|
|
529
|
+
- `status`: HTTP status code
|
|
530
|
+
- `statusText`: HTTP status text
|
|
531
|
+
- `headers`: Response headers string
|
|
532
|
+
- `xhr`: The XMLHttpRequest instance
|
|
480
533
|
|
|
481
534
|
**Example:**
|
|
535
|
+
|
|
482
536
|
```javascript
|
|
483
537
|
import RequestManager from '@enegalan/request-manager';
|
|
484
538
|
|
|
485
539
|
const requestManager = new RequestManager();
|
|
486
540
|
|
|
487
541
|
// Simple GET request
|
|
488
|
-
requestManager
|
|
489
|
-
|
|
490
|
-
|
|
542
|
+
requestManager
|
|
543
|
+
.xhr('/api/users')
|
|
544
|
+
.then((response) => console.log(response.data))
|
|
545
|
+
.catch((error) => console.error(error));
|
|
491
546
|
|
|
492
547
|
// POST request with options
|
|
493
|
-
requestManager
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
548
|
+
requestManager
|
|
549
|
+
.xhr('/api/users', {
|
|
550
|
+
method: 'POST',
|
|
551
|
+
headers: { 'Content-Type': 'application/json' },
|
|
552
|
+
body: JSON.stringify({ name: 'John' }),
|
|
553
|
+
responseType: 'json',
|
|
554
|
+
})
|
|
555
|
+
.then((response) => console.log(response.data));
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### `getRequestId(url, options)`
|
|
559
|
+
|
|
560
|
+
Returns the request ID that RequestManager assigns for a URL and options.
|
|
561
|
+
|
|
562
|
+
**Parameters:**
|
|
563
|
+
|
|
564
|
+
- `url` (string): The URL used when starting the request
|
|
565
|
+
- `options` (Object, optional): Same options used for the request
|
|
566
|
+
- `requestKey` (string|number|Function, optional): Key override
|
|
567
|
+
- `includeQuery` (boolean, optional): Keep query string in the URL-based ID
|
|
568
|
+
- `noCancel` (boolean, optional): If true, returns a **new** unique ID (will not match an already in-flight `noCancel` request)
|
|
569
|
+
|
|
570
|
+
**Returns:** `string` — the request identifier
|
|
571
|
+
|
|
572
|
+
**Example:**
|
|
573
|
+
|
|
574
|
+
```javascript
|
|
575
|
+
requestManager.fetch('/api/users');
|
|
576
|
+
|
|
577
|
+
const id = requestManager.getRequestId('/api/users');
|
|
578
|
+
if (requestManager.isActive(id)) {
|
|
579
|
+
requestManager.cancel(id);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// With the same options used for the request:
|
|
583
|
+
const searchId = requestManager.getRequestId('/api/search?q=test', { requestKey: 'search-users' });
|
|
584
|
+
requestManager.cancel(searchId);
|
|
500
585
|
```
|
|
501
586
|
|
|
502
587
|
### `cancel(requestId)`
|
|
@@ -504,6 +589,7 @@ requestManager.xhr('/api/users', {
|
|
|
504
589
|
Cancels a specific request by its identifier.
|
|
505
590
|
|
|
506
591
|
**Parameters:**
|
|
592
|
+
|
|
507
593
|
- `requestId` (string): The unique identifier of the request to cancel
|
|
508
594
|
|
|
509
595
|
**Returns:** `true` if the request was found and cancelled, `false` otherwise
|
|
@@ -519,6 +605,7 @@ Cancels all active requests.
|
|
|
519
605
|
Checks if a request with the given identifier is currently active.
|
|
520
606
|
|
|
521
607
|
**Parameters:**
|
|
608
|
+
|
|
522
609
|
- `requestId` (string): The unique identifier to check
|
|
523
610
|
|
|
524
611
|
**Returns:** `true` if the request is active, `false` otherwise
|
|
@@ -535,11 +622,12 @@ Clears all active requests without cancelling them. Use with caution - this will
|
|
|
535
622
|
|
|
536
623
|
### `getSignal()`
|
|
537
624
|
|
|
538
|
-
|
|
625
|
+
Creates a new AbortController and returns its signal for the next `request()` (one `getSignal` → one request). Do not use for parallel requests; use `fetch()`, `axios()`, or `request(url, ({ options }) => ...)` instead — they create their own signal.
|
|
539
626
|
|
|
540
|
-
**Returns:** AbortSignal from
|
|
627
|
+
**Returns:** AbortSignal from a new AbortController
|
|
541
628
|
|
|
542
629
|
**Example:**
|
|
630
|
+
|
|
543
631
|
```javascript
|
|
544
632
|
const signal = requestManager.getSignal();
|
|
545
633
|
requestManager.request('/api/users', fetch('/api/users', { signal }));
|
|
@@ -547,11 +635,12 @@ requestManager.request('/api/users', fetch('/api/users', { signal }));
|
|
|
547
635
|
|
|
548
636
|
### `getAbortController()`
|
|
549
637
|
|
|
550
|
-
|
|
638
|
+
Creates a new AbortController for the next request handoff. Always returns a fresh controller (never reuses one from another in-flight request).
|
|
551
639
|
|
|
552
640
|
**Returns:** AbortController instance
|
|
553
641
|
|
|
554
642
|
**Example:**
|
|
643
|
+
|
|
555
644
|
```javascript
|
|
556
645
|
const abortController = requestManager.getAbortController();
|
|
557
646
|
requestManager.request('/api/users', fetch('/api/users', { signal: abortController.signal }));
|
|
@@ -568,17 +657,19 @@ Gets the manager options that were passed to the constructor or set via `setOpti
|
|
|
568
657
|
Sets the manager options.
|
|
569
658
|
|
|
570
659
|
**Parameters:**
|
|
660
|
+
|
|
571
661
|
- `options` (Object): Configuration options
|
|
572
|
-
|
|
662
|
+
- `verbose` (boolean, optional): If true, cancellation rejects with a message that includes the request id. If false (default), cancellation is silent.
|
|
573
663
|
|
|
574
664
|
**Example:**
|
|
665
|
+
|
|
575
666
|
```javascript
|
|
576
667
|
const requestManager = new RequestManager();
|
|
577
668
|
|
|
578
|
-
// Enable verbose
|
|
669
|
+
// Enable verbose cancellation messages at runtime
|
|
579
670
|
requestManager.setOptions({ verbose: true });
|
|
580
671
|
|
|
581
|
-
//
|
|
672
|
+
// Silent cancellation (default) — no rejection / no console noise
|
|
582
673
|
requestManager.setOptions({ verbose: false });
|
|
583
674
|
```
|
|
584
675
|
|
|
@@ -587,10 +678,12 @@ requestManager.setOptions({ verbose: false });
|
|
|
587
678
|
Links an abort signal with an HTTP client abort method. Useful for custom HTTP clients that only support the abort method to cancel requests.
|
|
588
679
|
|
|
589
680
|
**Parameters:**
|
|
681
|
+
|
|
590
682
|
- `abortMethod` (Function): The abort method to call when the signal is aborted
|
|
591
683
|
- `signal` (AbortSignal): The signal to listen to
|
|
592
684
|
|
|
593
685
|
**Example:**
|
|
686
|
+
|
|
594
687
|
```javascript
|
|
595
688
|
const abortController = new AbortController();
|
|
596
689
|
const req = $.ajax({ url });
|