npm-axios

Description
Promise based HTTP client for the browser and node.js
Author
tessl
Last updated

How to use

npx @tessl/cli registry install tessl/npm-axios@1.6.0

request-cancellation.md docs/

1
# Request Cancellation
2
3
Request cancellation support using modern AbortController and legacy CancelToken for aborting in-flight requests. This allows you to cancel requests that are no longer needed, helping to reduce unnecessary network traffic and improve performance.
4
5
## Capabilities
6
7
### Modern Cancellation with AbortController
8
9
The recommended approach using the standard AbortController API.
10
11
```typescript { .api }
12
interface GenericAbortSignal {
13
readonly aborted: boolean;
14
onabort?: ((...args: any) => any) | null;
15
addEventListener?: (...args: any) => any;
16
removeEventListener?: (...args: any) => any;
17
}
18
```
19
20
**Usage Examples:**
21
22
```typescript
23
import axios from "axios";
24
25
// Basic cancellation
26
const controller = new AbortController();
27
28
const request = axios.get("https://api.example.com/data", {
29
signal: controller.signal
30
});
31
32
// Cancel the request
33
controller.abort();
34
35
try {
36
const response = await request;
37
console.log(response.data);
38
} catch (error) {
39
if (axios.isCancel(error)) {
40
console.log("Request was cancelled");
41
} else {
42
console.error("Request failed:", error);
43
}
44
}
45
46
// Timeout with AbortController
47
function requestWithTimeout<T>(url: string, timeout: number) {
48
const controller = new AbortController();
49
50
// Auto-cancel after timeout
51
const timeoutId = setTimeout(() => {
52
controller.abort();
53
}, timeout);
54
55
return axios.get<T>(url, { signal: controller.signal })
56
.finally(() => {
57
clearTimeout(timeoutId);
58
});
59
}
60
61
// Usage
62
try {
63
const response = await requestWithTimeout("https://api.example.com/slow-endpoint", 5000);
64
console.log(response.data);
65
} catch (error) {
66
if (axios.isCancel(error)) {
67
console.log("Request timed out");
68
}
69
}
70
71
// Multiple requests with shared cancellation
72
const controller = new AbortController();
73
74
const requests = [
75
"https://api.example.com/users",
76
"https://api.example.com/posts",
77
"https://api.example.com/comments"
78
].map(url => axios.get(url, { signal: controller.signal }));
79
80
// Cancel all requests
81
setTimeout(() => {
82
controller.abort();
83
}, 2000);
84
85
try {
86
const responses = await Promise.all(requests);
87
console.log("All requests completed:", responses);
88
} catch (error) {
89
if (axios.isCancel(error)) {
90
console.log("Requests were cancelled");
91
}
92
}
93
```
94
95
### Legacy Cancellation with CancelToken
96
97
Legacy cancellation mechanism maintained for backward compatibility.
98
99
```typescript { .api }
100
class CancelToken {
101
/**
102
* Creates new CancelToken
103
* @param executor - Function that receives cancel function
104
*/
105
constructor(executor: (cancel: Canceler) => void);
106
107
/** Promise that resolves when cancelled */
108
promise: Promise<Cancel>;
109
110
/** Cancellation reason if cancelled */
111
reason?: Cancel;
112
113
/** Throw error if already cancelled */
114
throwIfRequested(): void;
115
116
/**
117
* Create cancellation source
118
* @returns Object with token and cancel function
119
*/
120
static source(): CancelTokenSource;
121
}
122
123
interface CancelTokenSource {
124
token: CancelToken;
125
cancel: Canceler;
126
}
127
128
interface Canceler {
129
(message?: string, config?: AxiosRequestConfig, request?: any): void;
130
}
131
132
interface Cancel {
133
message: string | undefined;
134
}
135
```
136
137
**Usage Examples:**
138
139
```typescript
140
import axios, { CancelToken } from "axios";
141
142
// Using CancelToken.source()
143
const source = CancelToken.source();
144
145
const request = axios.get("https://api.example.com/data", {
146
cancelToken: source.token
147
});
148
149
// Cancel the request
150
source.cancel("Request cancelled by user");
151
152
try {
153
const response = await request;
154
console.log(response.data);
155
} catch (error) {
156
if (axios.isCancel(error)) {
157
console.log("Cancelled:", error.message);
158
}
159
}
160
161
// Using CancelToken constructor
162
let cancel: Canceler;
163
164
const request = axios.get("https://api.example.com/data", {
165
cancelToken: new CancelToken((c) => {
166
cancel = c;
167
})
168
});
169
170
// Cancel later
171
setTimeout(() => {
172
cancel("Timeout");
173
}, 5000);
174
175
// Check if token is already cancelled
176
const token = source.token;
177
try {
178
token.throwIfRequested();
179
// Token is not cancelled, proceed with request
180
} catch (error) {
181
console.log("Token was already cancelled");
182
}
183
```
184
185
### CanceledError Class
186
187
Specific error type for cancelled requests.
188
189
```typescript { .api }
190
class CanceledError<T> extends AxiosError<T> {
191
// Inherits all properties and methods from AxiosError
192
}
193
```
194
195
**Usage Examples:**
196
197
```typescript
198
import axios, { CanceledError, isCancel } from "axios";
199
200
const controller = new AbortController();
201
202
try {
203
const response = axios.get("https://api.example.com/data", {
204
signal: controller.signal
205
});
206
207
// Cancel the request
208
controller.abort();
209
210
await response;
211
} catch (error) {
212
if (error instanceof CanceledError) {
213
console.log("Request was cancelled specifically");
214
} else if (isCancel(error)) {
215
console.log("Request cancellation detected");
216
} else {
217
console.log("Other error occurred");
218
}
219
}
220
```
221
222
### Cancellation Detection
223
224
Utility functions to detect if an error is due to cancellation.
225
226
```typescript { .api }
227
/**
228
* Check if error is a cancellation
229
* @param value - Value to check
230
* @returns Type predicate indicating if value is Cancel
231
*/
232
function isCancel(value: any): value is Cancel;
233
```
234
235
**Usage Examples:**
236
237
```typescript
238
import axios, { isCancel } from "axios";
239
240
async function fetchData(signal?: AbortSignal) {
241
try {
242
const response = await axios.get("https://api.example.com/data", { signal });
243
return response.data;
244
} catch (error) {
245
if (isCancel(error)) {
246
console.log("Request was cancelled, ignoring error");
247
return null; // or some default value
248
}
249
throw error; // Re-throw non-cancellation errors
250
}
251
}
252
253
// Usage
254
const controller = new AbortController();
255
const dataPromise = fetchData(controller.signal);
256
257
// Cancel after 3 seconds
258
setTimeout(() => controller.abort(), 3000);
259
260
const data = await dataPromise; // Will be null if cancelled
261
```
262
263
### Advanced Cancellation Patterns
264
265
Complex cancellation scenarios and patterns.
266
267
**Usage Examples:**
268
269
```typescript
270
import axios from "axios";
271
272
// Cancellable request manager
273
class RequestManager {
274
private requests = new Map<string, AbortController>();
275
276
async makeRequest<T>(key: string, url: string, config?: any): Promise<T | null> {
277
// Cancel existing request with same key
278
this.cancelRequest(key);
279
280
// Create new controller
281
const controller = new AbortController();
282
this.requests.set(key, controller);
283
284
try {
285
const response = await axios.get<T>(url, {
286
...config,
287
signal: controller.signal
288
});
289
290
// Remove from tracking when complete
291
this.requests.delete(key);
292
return response.data;
293
} catch (error) {
294
this.requests.delete(key);
295
296
if (axios.isCancel(error)) {
297
console.log(`Request ${key} was cancelled`);
298
return null;
299
}
300
throw error;
301
}
302
}
303
304
cancelRequest(key: string): void {
305
const controller = this.requests.get(key);
306
if (controller) {
307
controller.abort();
308
this.requests.delete(key);
309
}
310
}
311
312
cancelAllRequests(): void {
313
for (const [key, controller] of this.requests) {
314
controller.abort();
315
}
316
this.requests.clear();
317
}
318
}
319
320
// Usage
321
const manager = new RequestManager();
322
323
// Make requests
324
const users = manager.makeRequest("users", "https://api.example.com/users");
325
const posts = manager.makeRequest("posts", "https://api.example.com/posts");
326
327
// Cancel specific request
328
manager.cancelRequest("users");
329
330
// Cancel all pending requests
331
manager.cancelAllRequests();
332
333
// Search with automatic cancellation
334
class SearchService {
335
private currentController?: AbortController;
336
337
async search(query: string): Promise<any[]> {
338
// Cancel previous search
339
if (this.currentController) {
340
this.currentController.abort();
341
}
342
343
// Create new controller for this search
344
this.currentController = new AbortController();
345
346
try {
347
const response = await axios.get("https://api.example.com/search", {
348
params: { q: query },
349
signal: this.currentController.signal
350
});
351
352
return response.data.results;
353
} catch (error) {
354
if (axios.isCancel(error)) {
355
console.log(`Search for "${query}" was cancelled`);
356
return [];
357
}
358
throw error;
359
} finally {
360
this.currentController = undefined;
361
}
362
}
363
}
364
365
const searchService = new SearchService();
366
367
// Rapid searches - previous ones get cancelled automatically
368
searchService.search("a");
369
searchService.search("ab");
370
searchService.search("abc"); // Only this one will complete
371
372
// Timeout wrapper
373
function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
374
const controller = new AbortController();
375
376
const timeoutPromise = new Promise<never>((_, reject) => {
377
setTimeout(() => {
378
controller.abort();
379
reject(new Error(`Operation timed out after ${ms}ms`));
380
}, ms);
381
});
382
383
return Promise.race([promise, timeoutPromise]);
384
}
385
386
// Usage
387
try {
388
const data = await withTimeout(
389
axios.get("https://api.example.com/slow-endpoint"),
390
5000
391
);
392
console.log(data.data);
393
} catch (error) {
394
if (error.message.includes("timed out")) {
395
console.log("Request timed out");
396
}
397
}
398
399
// Component cleanup pattern (React example)
400
class DataComponent {
401
private controller?: AbortController;
402
403
async loadData() {
404
// Cancel previous request
405
if (this.controller) {
406
this.controller.abort();
407
}
408
409
this.controller = new AbortController();
410
411
try {
412
const response = await axios.get("https://api.example.com/data", {
413
signal: this.controller.signal
414
});
415
416
// Update component state
417
this.setState({ data: response.data });
418
} catch (error) {
419
if (!axios.isCancel(error)) {
420
// Handle real errors
421
this.setState({ error: error.message });
422
}
423
}
424
}
425
426
cleanup() {
427
// Cancel any pending requests when component unmounts
428
if (this.controller) {
429
this.controller.abort();
430
this.controller = undefined;
431
}
432
}
433
}
434
```
435
436
### Cancellation with Promise.race
437
438
Using cancellation with Promise.race for advanced patterns.
439
440
**Usage Examples:**
441
442
```typescript
443
import axios from "axios";
444
445
// Race between multiple endpoints
446
async function fetchFromMultipleEndpoints(urls: string[]): Promise<any> {
447
const controller = new AbortController();
448
449
const requests = urls.map(url =>
450
axios.get(url, { signal: controller.signal })
451
.then(response => ({ url, data: response.data, success: true }))
452
.catch(error => ({ url, error, success: false }))
453
);
454
455
try {
456
// Return first successful response
457
const results = await Promise.all(requests);
458
const successful = results.find(r => r.success);
459
460
if (successful) {
461
// Cancel remaining requests
462
controller.abort();
463
return successful.data;
464
} else {
465
throw new Error("All endpoints failed");
466
}
467
} catch (error) {
468
controller.abort();
469
throw error;
470
}
471
}
472
473
// User-cancellable operation
474
async function longRunningOperation(onCancel: (cancel: () => void) => void): Promise<any> {
475
const controller = new AbortController();
476
477
// Provide cancel function to caller
478
onCancel(() => controller.abort());
479
480
const steps = [
481
() => axios.get("https://api.example.com/step1", { signal: controller.signal }),
482
() => axios.get("https://api.example.com/step2", { signal: controller.signal }),
483
() => axios.get("https://api.example.com/step3", { signal: controller.signal })
484
];
485
486
const results = [];
487
488
for (const step of steps) {
489
try {
490
const response = await step();
491
results.push(response.data);
492
} catch (error) {
493
if (axios.isCancel(error)) {
494
console.log("Operation cancelled by user");
495
return { cancelled: true, results };
496
}
497
throw error;
498
}
499
}
500
501
return { cancelled: false, results };
502
}
503
504
// Usage
505
let cancelOperation: (() => void) | undefined;
506
507
const operationPromise = longRunningOperation((cancel) => {
508
cancelOperation = cancel;
509
});
510
511
// User can cancel the operation
512
document.getElementById("cancelButton")?.addEventListener("click", () => {
513
if (cancelOperation) {
514
cancelOperation();
515
}
516
});
517
518
const result = await operationPromise;
519
if (result.cancelled) {
520
console.log("User cancelled the operation");
521
} else {
522
console.log("Operation completed:", result.results);
523
}
524
```