@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 CHANGED
@@ -3,15 +3,15 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@enegalan/request-manager.svg)](https://www.npmjs.com/package/@enegalan/request-manager)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- RequestManager is a JavaScript library designed to manage and regulate HTTP requests efficiently. It allows you to use HTTP calls from any library (ajax, Ext.Ajax, axios, fetch, etc.) by accepting Promises as parameters.
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**: Works with any HTTP library that returns a Promise (fetch, axios, Ext.Ajax, etc.)
11
- - **Automatic Cancellation**: When a request is repeated with the same identifier, the previous request is automatically cancelled
12
- - **Prioritizes Recent Requests**: The library ensures that only the most recent request is processed, cancelling older ones
13
- - **Simple API**: Easy to use and integrate into existing projects
14
- - **Adapt to your requirements**: The library supports `options` for custom request management
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
- const requestManager = new RequestManager();
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
- ### Basic Example with fetch()
67
+ ### Which method should I use?
66
68
 
67
- ```javascript
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
- const requestManager = new RequestManager();
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
- // Simple GET request - automatically uses cleaned URL as requestKey
73
- requestManager.fetch('/api/users')
74
- .then(response => response.json())
75
- .then(data => console.log(data))
76
- .catch(error => {
77
- if (error.message === 'Request was cancelled') {
78
- console.log('Request was cancelled');
79
- } else {
80
- console.error('Request failed:', error);
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
- ### POST Request with Options
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
- // POST request with options
93
- requestManager.fetch('/api/users', {
94
- method: 'POST',
95
- headers: { 'Content-Type': 'application/json' },
96
- body: JSON.stringify({ name: 'John' })
97
- })
98
- .then(response => response.json())
99
- .then(data => console.log(data));
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
- ### Using request() with Promise
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
- // Using request() with a Promise
110
- // The URL is used to generate the request ID (cleaned URL)
111
- requestManager.request('/api/users', fetch('/api/users'))
112
- .then(response => response.json())
113
- .then(data => console.log(data));
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() with Function
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
- // Using request() with a Function - allows custom logic
124
- // The function receives { options } where options contains the signal
125
- requestManager.request('/api/users', ({ options }) => {
126
- return fetch('/api/users', options);
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
- requestManager.fetch('/api/search?q=updated')
150
- .then(response => response.json())
151
- .then(data => console.log('Second request completed:', data));
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.fetch('/api/search?q=test', {
164
- requestKey: 'search-users' // Custom key instead of cleaned URL
165
- })
166
- .catch(error => {
167
- console.log('First request cancelled:', error.message);
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
- requestManager.fetch('/api/search?q=updated', {
173
- requestKey: 'search-users' // Same key = same request ID = cancellation
174
- })
175
- .then(response => response.json())
176
- .then(data => console.log('Second request completed:', data));
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
- return requestManager.fetch(`/api/search?q=${query}`, {
190
- requestKey: () => `search-${query}` // Function that returns the key
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.fetch('/api/lazy?load=1', { noCancel: true })
209
- .then(response => response.json())
210
- .then(data => console.log('Load 1:', data));
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.fetch('/api/lazy?load=2', { noCancel: true })
213
- .then(response => response.json())
214
- .then(data => console.log('Load 2:', data));
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.fetch('/api/lazy?load=3', { noCancel: true })
217
- .then(response => response.json())
218
- .then(data => console.log('Load 3:', data));
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 with Axios
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
- // Using axios with request() method
233
- // The URL is used to generate the request ID
234
- const CancelToken = axios.CancelToken;
235
- const source = CancelToken.source();
236
-
237
- requestManager.request('/api/users', axios.get('/api/users', {
238
- cancelToken: source.token
239
- }), {
240
- cancelToken: () => source.cancel()
241
- })
242
- .then(response => console.log(response.data))
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 and Function
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
- // Using axios with Function - The function receives { options } where options contains the signal
261
- requestManager.request('/api/users', ({ options }) => {
262
- const CancelToken = axios.CancelToken;
263
- const source = CancelToken.source();
264
- return axios.get('/api/users', { cancelToken: source.token });
265
- })
266
- .then(response => console.log(response.data))
267
- .catch(error => {
268
- if (axios.isCancel(error)) {
269
- console.log('Request was cancelled');
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 Other Libraries
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
- // Example with a custom HTTP library using Function
284
- // The function receives { options } where options contains the signal
285
- requestManager.request('/api/data', ({ options }) => {
286
- return new Promise((resolve, reject) => {
287
- const xhr = new XMLHttpRequest();
288
- xhr.open('GET', '/api/data');
289
- xhr.onload = () => resolve(xhr.responseText);
290
- xhr.onerror = () => reject(new Error('Request failed'));
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
- ### Using with Pre-created Promises
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
- // If you already have a Promise, you can pass it directly
312
- const existingPromise = fetch('/api/data');
313
-
314
- requestManager.request('/api/data', existingPromise, {
315
- // You can still provide cancelToken if your library supports it
316
- cancelToken: () => {
317
- // Custom cancellation logic
318
- }
319
- })
320
- .then(response => response.json())
321
- .then(data => console.log(data));
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
- - `verbose` (boolean, optional): If true, cancellation errors will include messages globally for all requests.
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
- Executes an HTTP request, cancelling any previous request with the same identifier.
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): The Promise returned by any HTTP library (fetch, axios, etc.), a Function that receives `{ options }` and returns a Promise, or a URL string (which will be used with fetch internally)
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
- - `abortController` (AbortController): AbortController instance (created automatically if not provided)
349
- - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
350
- - `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.
351
- - `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.
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
- - `requestKey` (string|number|Function, optional): Key to identify duplicate requests. If not provided, the cleaned URL is used as the key.
365
- - `abortController` (AbortController): AbortController instance (created automatically if not provided)
366
- - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
367
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
368
- - Any other properties are passed as fetch options (method, headers, body, etc.)
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
- - `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.
382
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
383
- - Any other properties are passed as axios options (method, headers, params, data, etc.)
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.axios('/api/users')
399
- .then(response => console.log(response.data))
400
- .catch(error => console.error(error));
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
- baseURL: 'https://api.example.com',
405
- timeout: 5000
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.axios('/api/users', {
413
- method: 'POST',
414
- data: { name: 'John' },
415
- headers: { 'Content-Type': 'application/json' }
416
- })
417
- .then(response => console.log(response.data));
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(ajaxMethod, url, options)`
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
- Executes an HTTP request using a custom ajax method function, cancelling any previous request with the same identifier.
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
- - `ajaxMethod` (Function): A function that receives `{ url, ...options }` and returns a Promise. The function should accept an object with `url` and other options including the `signal` (AbortSignal).
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
- - `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.
429
- - `abortController` (AbortController): AbortController instance (created automatically if not provided)
430
- - `cancelToken` (Function|Object): Cancel token or cancel function for other libraries
431
- - `verbose` (boolean): If true, cancellation errors will include messages
432
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
433
- - Any other properties are passed to the ajax method function
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
- // Using with jQuery.ajax
444
- requestManager.ajax(
445
- ({ url, ...options }) => {
446
- return new Promise((resolve, reject) => {
447
- $.ajax({
448
- url: url,
449
- ...options,
450
- success: resolve,
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
- - `method` (string): HTTP method (GET, POST, PUT, DELETE, etc.). Defaults to 'GET'.
473
- - `headers` (Object): Headers object to set on the request
474
- - `body` (string|FormData|Blob|ArrayBuffer): Request body
475
- - `responseType` (string): Response type ('text', 'json', 'blob', 'arraybuffer', 'document'). Defaults to 'text'.
476
- - `withCredentials` (boolean): Whether to send credentials with the request
477
- - `timeout` (number): Request timeout in milliseconds
478
- - `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.
479
- - `abortController` (AbortController): AbortController instance (created automatically if not provided)
480
- - `verbose` (boolean): If true, cancellation errors will include messages
481
- - `noCancel` (boolean): If true, this request will not cancel previous requests with the same ID, allowing concurrent requests
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
- - `data`: The response data (automatically parsed as JSON if Content-Type is application/json)
485
- - `status`: HTTP status code
486
- - `statusText`: HTTP status text
487
- - `headers`: Response headers string
488
- - `xhr`: The XMLHttpRequest instance
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.xhr('/api/users')
498
- .then(response => console.log(response.data))
499
- .catch(error => console.error(error));
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.xhr('/api/users', {
503
- method: 'POST',
504
- headers: { 'Content-Type': 'application/json' },
505
- body: JSON.stringify({ name: 'John' }),
506
- responseType: 'json'
507
- })
508
- .then(response => console.log(response.data));
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
- Gets the AbortSignal from the current AbortController. Creates a new AbortController if one doesn't exist or if the current one is aborted.
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 the current AbortController
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
- Gets the current AbortController instance. Creates a new AbortController if one doesn't exist or if the current one is aborted.
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
- - `verbose` (boolean, optional): If true, cancellation errors will include messages
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 mode at runtime
669
+ // Enable verbose cancellation messages at runtime
588
670
  requestManager.setOptions({ verbose: true });
589
671
 
590
- // Disable verbose mode
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 });