@enegalan/request-manager 1.0.2 → 1.0.10
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 +316 -232
- package/dist/request-manager.cjs.js +298 -363
- 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 +298 -363
- 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 +298 -363
- 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 +298 -363
- 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 +119 -70
- package/main.js +298 -364
- 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 identifier, the previous request is automatically cancelled
|
|
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
|
|
|
@@ -55,78 +58,104 @@ import RequestManager, { RequestOptions, XhrResponse } from '@enegalan/request-m
|
|
|
55
58
|
|
|
56
59
|
const requestManager = new RequestManager({ verbose: true });
|
|
57
60
|
|
|
58
|
-
// Types are automatically inferred
|
|
59
61
|
const response: Response = await requestManager.fetch('/api/users');
|
|
60
62
|
const xhrResult: XhrResponse<{ name: string }> = await requestManager.xhr('/api/user/1');
|
|
61
63
|
```
|
|
62
64
|
|
|
63
65
|
## Usage
|
|
64
66
|
|
|
65
|
-
###
|
|
67
|
+
### Which method should I use?
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
import RequestManager from '@enegalan/request-manager';
|
|
69
|
+
Pick the **dedicated helper** for your HTTP client. Use `request()` only when none of the helpers fit.
|
|
69
70
|
|
|
70
|
-
|
|
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 axios `CancelToken` and wires cancel for you |
|
|
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.
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
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 }));
|
|
83
95
|
```
|
|
84
96
|
|
|
85
|
-
|
|
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()
|
|
86
101
|
|
|
87
102
|
```javascript
|
|
88
103
|
import RequestManager from '@enegalan/request-manager';
|
|
89
104
|
|
|
90
|
-
const requestManager = new RequestManager();
|
|
105
|
+
const requestManager = new RequestManager({ verbose: true });
|
|
91
106
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
|
|
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
|
+
});
|
|
100
118
|
```
|
|
101
119
|
|
|
102
|
-
###
|
|
120
|
+
### POST Request with Options
|
|
103
121
|
|
|
104
122
|
```javascript
|
|
105
123
|
import RequestManager from '@enegalan/request-manager';
|
|
106
124
|
|
|
107
125
|
const requestManager = new RequestManager();
|
|
108
126
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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));
|
|
114
135
|
```
|
|
115
136
|
|
|
116
|
-
### Using request()
|
|
137
|
+
### Using request()
|
|
138
|
+
|
|
139
|
+
`request()` is the low-level API when you already have a Promise or need custom wiring.
|
|
117
140
|
|
|
118
141
|
```javascript
|
|
119
142
|
import RequestManager from '@enegalan/request-manager';
|
|
120
143
|
|
|
121
144
|
const requestManager = new RequestManager();
|
|
122
145
|
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
})
|
|
128
|
-
.then(response => response.json())
|
|
129
|
-
.then(data => console.log(data));
|
|
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
|
+
});
|
|
130
159
|
```
|
|
131
160
|
|
|
132
161
|
### Automatic Cancellation with Same URL
|
|
@@ -138,17 +167,17 @@ const requestManager = new RequestManager();
|
|
|
138
167
|
|
|
139
168
|
// By default, requests with the same URL (cleaned) will cancel previous ones
|
|
140
169
|
// The URL is automatically cleaned (protocol and query params removed) to generate the request ID
|
|
141
|
-
requestManager.fetch('/api/search?q=test')
|
|
142
|
-
.catch(error => {
|
|
170
|
+
requestManager.fetch('/api/search?q=test').catch((error) => {
|
|
143
171
|
console.log('First request cancelled:', error.message);
|
|
144
|
-
|
|
172
|
+
});
|
|
145
173
|
|
|
146
174
|
// This second request will automatically cancel the first one
|
|
147
175
|
// because they share the same cleaned URL
|
|
148
176
|
setTimeout(() => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
177
|
+
requestManager
|
|
178
|
+
.fetch('/api/search?q=updated')
|
|
179
|
+
.then((response) => response.json())
|
|
180
|
+
.then((data) => console.log('Second request completed:', data));
|
|
152
181
|
}, 100);
|
|
153
182
|
```
|
|
154
183
|
|
|
@@ -160,20 +189,22 @@ import RequestManager from '@enegalan/request-manager';
|
|
|
160
189
|
const requestManager = new RequestManager();
|
|
161
190
|
|
|
162
191
|
// You can use requestKey to override the default URL-based ID generation
|
|
163
|
-
requestManager
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
+
});
|
|
169
199
|
|
|
170
200
|
// This second request will cancel the first one because they share the same requestKey
|
|
171
201
|
setTimeout(() => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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));
|
|
177
208
|
}, 100);
|
|
178
209
|
```
|
|
179
210
|
|
|
@@ -186,9 +217,9 @@ const requestManager = new RequestManager();
|
|
|
186
217
|
|
|
187
218
|
// You can use a function to generate the requestKey dynamically
|
|
188
219
|
function searchUsers(query) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
220
|
+
return requestManager.fetch(`/api/search?q=${query}`, {
|
|
221
|
+
requestKey: () => `search-${query}`, // Function that returns the key
|
|
222
|
+
});
|
|
192
223
|
}
|
|
193
224
|
|
|
194
225
|
// Both calls will share the same requestKey and cancel each other
|
|
@@ -205,51 +236,46 @@ const requestManager = new RequestManager();
|
|
|
205
236
|
|
|
206
237
|
// Use noCancel: true to allow multiple requests to execute concurrently
|
|
207
238
|
// This is useful for lazy loading scenarios where you want all requests to complete
|
|
208
|
-
requestManager
|
|
209
|
-
|
|
210
|
-
|
|
239
|
+
requestManager
|
|
240
|
+
.fetch('/api/lazy?load=1', { noCancel: true })
|
|
241
|
+
.then((response) => response.json())
|
|
242
|
+
.then((data) => console.log('Load 1:', data));
|
|
211
243
|
|
|
212
|
-
requestManager
|
|
213
|
-
|
|
214
|
-
|
|
244
|
+
requestManager
|
|
245
|
+
.fetch('/api/lazy?load=2', { noCancel: true })
|
|
246
|
+
.then((response) => response.json())
|
|
247
|
+
.then((data) => console.log('Load 2:', data));
|
|
215
248
|
|
|
216
|
-
requestManager
|
|
217
|
-
|
|
218
|
-
|
|
249
|
+
requestManager
|
|
250
|
+
.fetch('/api/lazy?load=3', { noCancel: true })
|
|
251
|
+
.then((response) => response.json())
|
|
252
|
+
.then((data) => console.log('Load 3:', data));
|
|
219
253
|
|
|
220
254
|
// All three requests will execute concurrently without canceling each other
|
|
221
255
|
// Even though they share the same cleaned URL (without query params)
|
|
222
256
|
```
|
|
223
257
|
|
|
224
|
-
### Using
|
|
258
|
+
### Using includeQuery to Distinguish Query Strings
|
|
225
259
|
|
|
226
260
|
```javascript
|
|
227
|
-
import axios from 'axios';
|
|
228
261
|
import RequestManager from '@enegalan/request-manager';
|
|
229
262
|
|
|
230
263
|
const requestManager = new RequestManager();
|
|
231
264
|
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
requestManager.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
})
|
|
242
|
-
.
|
|
243
|
-
.catch(error => {
|
|
244
|
-
if (axios.isCancel(error)) {
|
|
245
|
-
console.log('Request was cancelled');
|
|
246
|
-
} else {
|
|
247
|
-
console.error('Request failed:', error);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
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
|
|
250
276
|
```
|
|
251
277
|
|
|
252
|
-
### Using with Axios
|
|
278
|
+
### Using with Axios
|
|
253
279
|
|
|
254
280
|
```javascript
|
|
255
281
|
import axios from 'axios';
|
|
@@ -257,68 +283,73 @@ import RequestManager from '@enegalan/request-manager';
|
|
|
257
283
|
|
|
258
284
|
const requestManager = new RequestManager();
|
|
259
285
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
} else {
|
|
271
|
-
console.error('Request failed:', error);
|
|
272
|
-
}
|
|
273
|
-
});
|
|
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
|
+
});
|
|
274
296
|
```
|
|
275
297
|
|
|
276
|
-
### 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`).
|
|
277
301
|
|
|
278
302
|
```javascript
|
|
279
303
|
import RequestManager from '@enegalan/request-manager';
|
|
280
304
|
|
|
281
305
|
const requestManager = new RequestManager();
|
|
282
306
|
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
xhr.send();
|
|
292
|
-
|
|
293
|
-
// Use signal to cancel if needed
|
|
294
|
-
options.signal.addEventListener('abort', () => {
|
|
295
|
-
xhr.abort();
|
|
296
|
-
reject(new Error('Request was cancelled'));
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
})
|
|
300
|
-
.then(data => console.log(data))
|
|
301
|
-
.catch(error => console.error(error));
|
|
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');
|
|
302
315
|
```
|
|
303
316
|
|
|
304
|
-
|
|
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
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
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`).
|
|
305
330
|
|
|
306
331
|
```javascript
|
|
307
332
|
import RequestManager from '@enegalan/request-manager';
|
|
308
333
|
|
|
309
334
|
const requestManager = new RequestManager();
|
|
310
335
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
.
|
|
321
|
-
.
|
|
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));
|
|
322
353
|
```
|
|
323
354
|
|
|
324
355
|
## API Reference
|
|
@@ -328,10 +359,12 @@ requestManager.request('/api/data', existingPromise, {
|
|
|
328
359
|
Creates a new RequestManager instance.
|
|
329
360
|
|
|
330
361
|
**Parameters:**
|
|
362
|
+
|
|
331
363
|
- `options` (Object, optional): Configuration options
|
|
332
|
-
|
|
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).
|
|
333
365
|
|
|
334
366
|
**Example:**
|
|
367
|
+
|
|
335
368
|
```javascript
|
|
336
369
|
// Create with verbose mode enabled
|
|
337
370
|
const requestManager = new RequestManager({ verbose: true });
|
|
@@ -339,33 +372,40 @@ const requestManager = new RequestManager({ verbose: true });
|
|
|
339
372
|
|
|
340
373
|
### `request(url, requestPromise, options)`
|
|
341
374
|
|
|
342
|
-
|
|
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.
|
|
343
376
|
|
|
344
377
|
**Parameters:**
|
|
378
|
+
|
|
345
379
|
- `url` (string): The URL of the request (used to generate request ID from cleaned URL)
|
|
346
|
-
- `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)
|
|
347
381
|
- `options` (Object, optional): Configuration options
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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.
|
|
387
|
+
|
|
388
|
+
> [!TIP]
|
|
389
|
+
> When `requestPromise` is a Function, you can pass custom properties in `options`. These will be accessible inside the callback via the `{ options }` parameter.
|
|
352
390
|
|
|
353
391
|
**Returns:** Promise that resolves/rejects based on the most recent request
|
|
354
392
|
|
|
355
|
-
**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.
|
|
356
394
|
|
|
357
395
|
### `fetch(url, options)`
|
|
358
396
|
|
|
359
397
|
Executes an HTTP request using fetch, cancelling any previous request with the same identifier.
|
|
360
398
|
|
|
361
399
|
**Parameters:**
|
|
400
|
+
|
|
362
401
|
- `url` (string): The URL to fetch
|
|
363
402
|
- `options` (Object, optional): Configuration options (same as `request()` method)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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.)
|
|
369
409
|
|
|
370
410
|
**Returns:** Promise that resolves/rejects based on the most recent request
|
|
371
411
|
|
|
@@ -376,11 +416,13 @@ Executes an HTTP request using fetch, cancelling any previous request with the s
|
|
|
376
416
|
Executes an HTTP request using axios, cancelling any previous request with the same identifier.
|
|
377
417
|
|
|
378
418
|
**Parameters:**
|
|
419
|
+
|
|
379
420
|
- `url` (string): The URL to request
|
|
380
421
|
- `options` (Object, optional): Configuration options
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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.)
|
|
384
426
|
- `axiosInstance` (Object, optional): Custom axios instance to use. If not provided, uses the global `axios` object.
|
|
385
427
|
|
|
386
428
|
**Returns:** Promise that resolves/rejects based on the most recent request
|
|
@@ -388,6 +430,7 @@ Executes an HTTP request using axios, cancelling any previous request with the s
|
|
|
388
430
|
**Note:** This method automatically creates a CancelToken for axios cancellation. 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.
|
|
389
431
|
|
|
390
432
|
**Example:**
|
|
433
|
+
|
|
391
434
|
```javascript
|
|
392
435
|
import axios from 'axios';
|
|
393
436
|
import RequestManager from '@enegalan/request-manager';
|
|
@@ -395,71 +438,69 @@ import RequestManager from '@enegalan/request-manager';
|
|
|
395
438
|
const requestManager = new RequestManager();
|
|
396
439
|
|
|
397
440
|
// Simple GET request (uses global axios)
|
|
398
|
-
requestManager
|
|
399
|
-
|
|
400
|
-
|
|
441
|
+
requestManager
|
|
442
|
+
.axios('/api/users')
|
|
443
|
+
.then((response) => console.log(response.data))
|
|
444
|
+
.catch((error) => console.error(error));
|
|
401
445
|
|
|
402
446
|
// With custom axios instance
|
|
403
447
|
const apiClient = axios.create({
|
|
404
|
-
|
|
405
|
-
|
|
448
|
+
baseURL: 'https://api.example.com',
|
|
449
|
+
timeout: 5000,
|
|
406
450
|
});
|
|
407
451
|
|
|
408
|
-
requestManager.axios('/users', {}, apiClient)
|
|
409
|
-
.then(response => console.log(response.data));
|
|
452
|
+
requestManager.axios('/users', {}, apiClient).then((response) => console.log(response.data));
|
|
410
453
|
|
|
411
454
|
// POST request with options
|
|
412
|
-
requestManager
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
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));
|
|
418
462
|
```
|
|
419
463
|
|
|
420
|
-
### `ajax(
|
|
464
|
+
### `ajax(ajaxFunction, url, options)`
|
|
465
|
+
|
|
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:
|
|
421
469
|
|
|
422
|
-
|
|
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`
|
|
423
473
|
|
|
424
474
|
**Parameters:**
|
|
425
|
-
|
|
475
|
+
|
|
476
|
+
- `ajaxFunction` (Function): Receives `{ url, ...options }` and returns the library request object (or a Promise).
|
|
426
477
|
- `url` (string): The URL to request
|
|
427
478
|
- `options` (Object, optional): Configuration options
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
|
434
486
|
|
|
435
487
|
**Returns:** Promise that resolves/rejects based on the most recent request
|
|
436
488
|
|
|
437
489
|
**Example:**
|
|
490
|
+
|
|
438
491
|
```javascript
|
|
439
492
|
import RequestManager from '@enegalan/request-manager';
|
|
440
493
|
|
|
441
494
|
const requestManager = new RequestManager();
|
|
442
495
|
|
|
443
|
-
//
|
|
444
|
-
requestManager
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
error: reject
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
},
|
|
455
|
-
'/api/users',
|
|
456
|
-
{
|
|
457
|
-
method: 'GET',
|
|
458
|
-
headers: { 'Content-Type': 'application/json' }
|
|
459
|
-
}
|
|
460
|
-
)
|
|
461
|
-
.then(data => console.log(data))
|
|
462
|
-
.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');
|
|
463
504
|
```
|
|
464
505
|
|
|
465
506
|
### `xhr(url, options)`
|
|
@@ -467,45 +508,80 @@ requestManager.ajax(
|
|
|
467
508
|
Executes an HTTP request using XMLHttpRequest, cancelling any previous request with the same identifier.
|
|
468
509
|
|
|
469
510
|
**Parameters:**
|
|
511
|
+
|
|
470
512
|
- `url` (string): The URL to request
|
|
471
513
|
- `options` (Object, optional): Configuration options
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
|
482
525
|
|
|
483
526
|
**Returns:** Promise that resolves/rejects based on the most recent request. The resolved value is an object with:
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
|
489
533
|
|
|
490
534
|
**Example:**
|
|
535
|
+
|
|
491
536
|
```javascript
|
|
492
537
|
import RequestManager from '@enegalan/request-manager';
|
|
493
538
|
|
|
494
539
|
const requestManager = new RequestManager();
|
|
495
540
|
|
|
496
541
|
// Simple GET request
|
|
497
|
-
requestManager
|
|
498
|
-
|
|
499
|
-
|
|
542
|
+
requestManager
|
|
543
|
+
.xhr('/api/users')
|
|
544
|
+
.then((response) => console.log(response.data))
|
|
545
|
+
.catch((error) => console.error(error));
|
|
500
546
|
|
|
501
547
|
// POST request with options
|
|
502
|
-
requestManager
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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);
|
|
509
585
|
```
|
|
510
586
|
|
|
511
587
|
### `cancel(requestId)`
|
|
@@ -513,6 +589,7 @@ requestManager.xhr('/api/users', {
|
|
|
513
589
|
Cancels a specific request by its identifier.
|
|
514
590
|
|
|
515
591
|
**Parameters:**
|
|
592
|
+
|
|
516
593
|
- `requestId` (string): The unique identifier of the request to cancel
|
|
517
594
|
|
|
518
595
|
**Returns:** `true` if the request was found and cancelled, `false` otherwise
|
|
@@ -528,6 +605,7 @@ Cancels all active requests.
|
|
|
528
605
|
Checks if a request with the given identifier is currently active.
|
|
529
606
|
|
|
530
607
|
**Parameters:**
|
|
608
|
+
|
|
531
609
|
- `requestId` (string): The unique identifier to check
|
|
532
610
|
|
|
533
611
|
**Returns:** `true` if the request is active, `false` otherwise
|
|
@@ -544,11 +622,12 @@ Clears all active requests without cancelling them. Use with caution - this will
|
|
|
544
622
|
|
|
545
623
|
### `getSignal()`
|
|
546
624
|
|
|
547
|
-
|
|
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.
|
|
548
626
|
|
|
549
|
-
**Returns:** AbortSignal from
|
|
627
|
+
**Returns:** AbortSignal from a new AbortController
|
|
550
628
|
|
|
551
629
|
**Example:**
|
|
630
|
+
|
|
552
631
|
```javascript
|
|
553
632
|
const signal = requestManager.getSignal();
|
|
554
633
|
requestManager.request('/api/users', fetch('/api/users', { signal }));
|
|
@@ -556,11 +635,12 @@ requestManager.request('/api/users', fetch('/api/users', { signal }));
|
|
|
556
635
|
|
|
557
636
|
### `getAbortController()`
|
|
558
637
|
|
|
559
|
-
|
|
638
|
+
Creates a new AbortController for the next request handoff. Always returns a fresh controller (never reuses one from another in-flight request).
|
|
560
639
|
|
|
561
640
|
**Returns:** AbortController instance
|
|
562
641
|
|
|
563
642
|
**Example:**
|
|
643
|
+
|
|
564
644
|
```javascript
|
|
565
645
|
const abortController = requestManager.getAbortController();
|
|
566
646
|
requestManager.request('/api/users', fetch('/api/users', { signal: abortController.signal }));
|
|
@@ -577,17 +657,19 @@ Gets the manager options that were passed to the constructor or set via `setOpti
|
|
|
577
657
|
Sets the manager options.
|
|
578
658
|
|
|
579
659
|
**Parameters:**
|
|
660
|
+
|
|
580
661
|
- `options` (Object): Configuration options
|
|
581
|
-
|
|
662
|
+
- `verbose` (boolean, optional): If true, cancellation rejects with a message that includes the request id. If false (default), cancellation is silent.
|
|
582
663
|
|
|
583
664
|
**Example:**
|
|
665
|
+
|
|
584
666
|
```javascript
|
|
585
667
|
const requestManager = new RequestManager();
|
|
586
668
|
|
|
587
|
-
// Enable verbose
|
|
669
|
+
// Enable verbose cancellation messages at runtime
|
|
588
670
|
requestManager.setOptions({ verbose: true });
|
|
589
671
|
|
|
590
|
-
//
|
|
672
|
+
// Silent cancellation (default) — no rejection / no console noise
|
|
591
673
|
requestManager.setOptions({ verbose: false });
|
|
592
674
|
```
|
|
593
675
|
|
|
@@ -596,10 +678,12 @@ requestManager.setOptions({ verbose: false });
|
|
|
596
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.
|
|
597
679
|
|
|
598
680
|
**Parameters:**
|
|
681
|
+
|
|
599
682
|
- `abortMethod` (Function): The abort method to call when the signal is aborted
|
|
600
683
|
- `signal` (AbortSignal): The signal to listen to
|
|
601
684
|
|
|
602
685
|
**Example:**
|
|
686
|
+
|
|
603
687
|
```javascript
|
|
604
688
|
const abortController = new AbortController();
|
|
605
689
|
const req = $.ajax({ url });
|