Skip to content

Commit 1c6a86d

Browse files
brodojasonsaayman
andauthored
fix: turn AxiosError into a native error (#5394) (#5558)
* fix: turn AxiosError into a native error (#5394) Being an object returned by the 'Error' constructor turns something into a 'native error'. * fix: simplify code in AxiosError * fix: simplify code in AxiosError * refactor: implement AxiosError as a class * refactor: implement CanceledError as a class This turns CanceledError into a native error. * refactor: simplify AxiosError.toJSON * fix: improve error code handling in `AxiosError.from` If no error code is provided, use the code from the underlying error. * fix: set error status in `AxiosError.constructor` If a response is passed to the constructor, set the response status as a property. * fix: remove unnecessary async --------- Co-authored-by: Jay <jasonsaayman@gmail.com>
1 parent 5b5c196 commit 1c6a86d

4 files changed

Lines changed: 99 additions & 120 deletions

File tree

lib/cancel/CanceledError.js

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
'use strict';
22

33
import AxiosError from '../core/AxiosError.js';
4-
import utils from '../utils.js';
54

6-
/**
7-
* A `CanceledError` is an object that is thrown when an operation is canceled.
8-
*
9-
* @param {string=} message The message.
10-
* @param {Object=} config The config.
11-
* @param {Object=} request The request.
12-
*
13-
* @returns {CanceledError} The created error.
14-
*/
15-
function CanceledError(message, config, request) {
16-
// eslint-disable-next-line no-eq-null,eqeqeq
17-
AxiosError.call(this, message == null ? 'canceled' : message, AxiosError.ERR_CANCELED, config, request);
18-
this.name = 'CanceledError';
5+
class CanceledError extends AxiosError {
6+
/**
7+
* A `CanceledError` is an object that is thrown when an operation is canceled.
8+
*
9+
* @param {string=} message The message.
10+
* @param {Object=} config The config.
11+
* @param {Object=} request The request.
12+
*
13+
* @returns {CanceledError} The created error.
14+
*/
15+
constructor(message, config, request) {
16+
super(message == null ? 'canceled' : message, AxiosError.ERR_CANCELED, config, request);
17+
this.name = 'CanceledError';
18+
this.__CANCEL__ = true;
19+
}
1920
}
2021

21-
utils.inherits(CanceledError, AxiosError, {
22-
__CANCEL__: true
23-
});
24-
2522
export default CanceledError;

lib/core/AxiosError.js

Lines changed: 65 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -2,109 +2,72 @@
22

33
import utils from '../utils.js';
44

5-
/**
6-
* Create an Error with the specified message, config, error code, request and response.
7-
*
8-
* @param {string} message The error message.
9-
* @param {string} [code] The error code (for example, 'ECONNABORTED').
10-
* @param {Object} [config] The config.
11-
* @param {Object} [request] The request.
12-
* @param {Object} [response] The response.
13-
*
14-
* @returns {Error} The created error.
15-
*/
16-
function AxiosError(message, code, config, request, response) {
17-
Error.call(this);
18-
19-
if (Error.captureStackTrace) {
20-
Error.captureStackTrace(this, this.constructor);
21-
} else {
22-
this.stack = (new Error()).stack;
23-
}
24-
25-
this.message = message;
26-
this.name = 'AxiosError';
27-
code && (this.code = code);
28-
config && (this.config = config);
29-
request && (this.request = request);
30-
if (response) {
31-
this.response = response;
32-
this.status = response.status ? response.status : null;
33-
}
5+
class AxiosError extends Error {
6+
static from(error, code, config, request, response, customProps) {
7+
const axiosError = new AxiosError(error.message, code || error.code, config, request, response);
8+
axiosError.cause = error;
9+
axiosError.name = error.name;
10+
customProps && Object.assign(axiosError, customProps);
11+
return axiosError;
12+
}
13+
14+
/**
15+
* Create an Error with the specified message, config, error code, request and response.
16+
*
17+
* @param {string} message The error message.
18+
* @param {string} [code] The error code (for example, 'ECONNABORTED').
19+
* @param {Object} [config] The config.
20+
* @param {Object} [request] The request.
21+
* @param {Object} [response] The response.
22+
*
23+
* @returns {Error} The created error.
24+
*/
25+
constructor(message, code, config, request, response) {
26+
super(message);
27+
this.name = 'AxiosError';
28+
this.isAxiosError = true;
29+
code && (this.code = code);
30+
config && (this.config = config);
31+
request && (this.request = request);
32+
if (response) {
33+
this.response = response;
34+
this.status = response.status;
35+
}
36+
}
37+
38+
toJSON() {
39+
return {
40+
// Standard
41+
message: this.message,
42+
name: this.name,
43+
// Microsoft
44+
description: this.description,
45+
number: this.number,
46+
// Mozilla
47+
fileName: this.fileName,
48+
lineNumber: this.lineNumber,
49+
columnNumber: this.columnNumber,
50+
stack: this.stack,
51+
// Axios
52+
config: utils.toJSONObject(this.config),
53+
code: this.code,
54+
status: this.status,
55+
};
56+
}
3457
}
3558

36-
utils.inherits(AxiosError, Error, {
37-
toJSON: function toJSON() {
38-
return {
39-
// Standard
40-
message: this.message,
41-
name: this.name,
42-
// Microsoft
43-
description: this.description,
44-
number: this.number,
45-
// Mozilla
46-
fileName: this.fileName,
47-
lineNumber: this.lineNumber,
48-
columnNumber: this.columnNumber,
49-
stack: this.stack,
50-
// Axios
51-
config: utils.toJSONObject(this.config),
52-
code: this.code,
53-
status: this.status
54-
};
55-
}
56-
});
57-
58-
const prototype = AxiosError.prototype;
59-
const descriptors = {};
60-
61-
[
62-
'ERR_BAD_OPTION_VALUE',
63-
'ERR_BAD_OPTION',
64-
'ECONNABORTED',
65-
'ETIMEDOUT',
66-
'ERR_NETWORK',
67-
'ERR_FR_TOO_MANY_REDIRECTS',
68-
'ERR_DEPRECATED',
69-
'ERR_BAD_RESPONSE',
70-
'ERR_BAD_REQUEST',
71-
'ERR_CANCELED',
72-
'ERR_NOT_SUPPORT',
73-
'ERR_INVALID_URL'
74-
// eslint-disable-next-line func-names
75-
].forEach(code => {
76-
descriptors[code] = {value: code};
77-
});
78-
79-
Object.defineProperties(AxiosError, descriptors);
80-
Object.defineProperty(prototype, 'isAxiosError', {value: true});
81-
82-
// eslint-disable-next-line func-names
83-
AxiosError.from = (error, code, config, request, response, customProps) => {
84-
const axiosError = Object.create(prototype);
85-
86-
utils.toFlatObject(error, axiosError, function filter(obj) {
87-
return obj !== Error.prototype;
88-
}, prop => {
89-
return prop !== 'isAxiosError';
90-
});
91-
92-
const msg = error && error.message ? error.message : 'Error';
93-
94-
// Prefer explicit code; otherwise copy the low-level error's code (e.g. ECONNREFUSED)
95-
const errCode = code == null && error ? error.code : code;
96-
AxiosError.call(axiosError, msg, errCode, config, request, response);
97-
98-
// Chain the original error on the standard field; non-enumerable to avoid JSON noise
99-
if (error && axiosError.cause == null) {
100-
Object.defineProperty(axiosError, 'cause', { value: error, configurable: true });
101-
}
102-
103-
axiosError.name = (error && error.name) || 'Error';
104-
105-
customProps && Object.assign(axiosError, customProps);
106-
107-
return axiosError;
108-
};
59+
// This can be changed to static properties as soon as the parser options in .eslint.cjs are updated.
60+
AxiosError.ERR_BAD_OPTION_VALUE = 'ERR_BAD_OPTION_VALUE';
61+
AxiosError.ERR_BAD_OPTION = 'ERR_BAD_OPTION';
62+
AxiosError.ECONNABORTED = 'ECONNABORTED';
63+
AxiosError.ETIMEDOUT = 'ETIMEDOUT';
64+
AxiosError.ERR_NETWORK = 'ERR_NETWORK';
65+
AxiosError.ERR_FR_TOO_MANY_REDIRECTS = 'ERR_FR_TOO_MANY_REDIRECTS';
66+
AxiosError.ERR_DEPRECATED = 'ERR_DEPRECATED';
67+
AxiosError.ERR_BAD_RESPONSE = 'ERR_BAD_RESPONSE';
68+
AxiosError.ERR_BAD_REQUEST = 'ERR_BAD_REQUEST';
69+
AxiosError.ERR_CANCELED = 'ERR_CANCELED';
70+
AxiosError.ERR_NOT_SUPPORT = 'ERR_NOT_SUPPORT';
71+
AxiosError.ERR_INVALID_URL = 'ERR_INVALID_URL';
10972

11073
export default AxiosError;

test/specs/cancel/CanceledError.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@ describe('Cancel', function() {
1212
expect(cancel.toString()).toBe('CanceledError: Operation has been canceled.');
1313
});
1414
});
15+
it('should be a native error as checked by the NodeJS `isNativeError` function', function (){
16+
if((typeof process !== 'undefined') && (process.release.name === 'node')){
17+
let {isNativeError} = require('node:util/types');
18+
expect(isNativeError(new CanceledError("My Canceled Error"))).toBeTruthy();
19+
}
20+
});
1521
});

test/specs/core/AxiosError.spec.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import AxiosError from '../../../lib/core/AxiosError';
22

3+
34
describe('core::AxiosError', function() {
45
it('should create an Error with message, config, code, request, response, stack and isAxiosError', function() {
56
const request = { path: '/foo' };
@@ -49,6 +50,18 @@ describe('core::AxiosError', function() {
4950
});
5051
});
5152

53+
it('should be a native error as checked by the NodeJS `isNativeError` function', function (){
54+
if((typeof process !== 'undefined') && (process.release.name === 'node')){
55+
let {isNativeError} = require('node:util/types');
56+
expect(isNativeError(new AxiosError("My Axios Error"))).toBeTruthy();
57+
}
58+
});
59+
60+
it('should create an error using one of the static class properties as an error code', function (){
61+
const myError = new AxiosError("My Axios Error", AxiosError.ECONNABORTED);
62+
expect(myError.code).toEqual(AxiosError.ECONNABORTED);
63+
});
64+
5265
it('should have status property when response was passed to the constructor', () => {
5366
const err = new AxiosError('test', 'foo', {}, {}, {status: 400});
5467
expect(err.status).toBe(400);

0 commit comments

Comments
 (0)