Tôi muốn ném một số thứ vào mã JS của mình và tôi muốn chúng là ví dụ Lỗi, nhưng tôi cũng muốn biến chúng thành một thứ khác.
Trong Python, thông thường, người ta sẽ phân lớp Ngoại lệ.
Điều thích hợp để làm trong JS là gì?
Tôi muốn ném một số thứ vào mã JS của mình và tôi muốn chúng là ví dụ Lỗi, nhưng tôi cũng muốn biến chúng thành một thứ khác.
Trong Python, thông thường, người ta sẽ phân lớp Ngoại lệ.
Điều thích hợp để làm trong JS là gì?
Câu trả lời:
Đối tượng lỗi trường tiêu chuẩn duy nhất có là thuộc message
tính. (Xem MDN hoặc Đặc tả ngôn ngữ EcmaScript, phần 15.11) Mọi thứ khác đều cụ thể theo nền tảng.
Môi trường mosts thiết lập các stack
tài sản, nhưng fileName
và lineNumber
là thực tế vô dụng được sử dụng trong kế thừa.
Vì vậy, cách tiếp cận tối giản là:
function MyError(message) {
this.name = 'MyError';
this.message = message;
this.stack = (new Error()).stack;
}
MyError.prototype = new Error; // <-- remove this if you do not
// want MyError to be instanceof Error
Bạn có thể đánh hơi ngăn xếp, hủy các phần tử không mong muốn khỏi nó và trích xuất thông tin như fileName và lineNumber, nhưng làm như vậy đòi hỏi thông tin về nền tảng JavaScript hiện đang chạy. Hầu hết các trường hợp không cần thiết - và bạn có thể làm điều đó trong khám nghiệm tử thi nếu bạn thực sự muốn.
Safari là một ngoại lệ đáng chú ý. Không có thuộc stack
tính, nhưng bộ throw
từ khóa sourceURL
và line
thuộc tính của đối tượng đang bị ném. Những điều đó được đảm bảo là chính xác.
Các trường hợp thử nghiệm tôi đã sử dụng có thể được tìm thấy ở đây: So sánh đối tượng Lỗi tự tạo JavaScript .
function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
.
this
, phải không?
Trong ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
thay thế.
Nói ngắn gọn:
Nếu bạn đang sử dụng ES6 mà không có bộ chuyển đổi :
class CustomError extends Error { /* ... */}
Nếu bạn đang sử dụng bộ chuyển mã Babel :
Tùy chọn 1: sử dụng babel-plugin-Transform-buildin-extend
Tùy chọn 2: tự làm (lấy cảm hứng từ cùng một thư viện)
function CustomError(...args) {
const instance = Reflect.construct(Error, args);
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
Reflect.setPrototypeOf(CustomError, Error);
Nếu bạn đang sử dụng ES5 thuần túy :
function CustomError(message, fileName, lineNumber) {
var instance = new Error(message, fileName, lineNumber);
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf){
Object.setPrototypeOf(CustomError, Error);
} else {
CustomError.__proto__ = Error;
}
Thay thế: sử dụng khung Classtrophobic
Giải trình:
Tại sao việc mở rộng lớp Error bằng ES6 và Babel là một vấn đề?
Bởi vì một phiên bản của CustomError không còn được công nhận như vậy nữa.
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
Trong thực tế, từ các tài liệu chính thức của Babel, bạn không thể mở rộng bất kỳ được xây dựng trong các lớp học JavaScript như Date
, Array
, DOM
hoặc Error
.
Vấn đề được mô tả ở đây:
Những câu trả lời SO khác thì sao?
Tất cả các câu trả lời đã khắc phục sự cố instanceof
nhưng bạn mất lỗi thông thường console.log
:
console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
Trong khi sử dụng phương pháp được đề cập ở trên, không chỉ bạn khắc phục sự cố instanceof
mà bạn còn giữ lỗi thường xuyên console.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
class CustomError extends Error { /* ... */}
không xử lý chính xác các đối số dành riêng cho nhà cung cấp ( lineNumber
, v.v.), 'Lỗi mở rộng trong Javascript với cú pháp ES6' là đặc thù của Babel, giải pháp ES5 của bạn sử dụng const
và nó không xử lý các đối số tùy chỉnh.
console.log(new CustomError('test') instanceof CustomError);// false
là đúng tại thời điểm viết nhưng hiện đã được giải quyết. Trong thực tế , vấn đề được liên kết trong câu trả lời đã được giải quyết và chúng ta có thể kiểm tra hành vi chính xác ở đây và bằng cách dán mã trong REPL và xem cách nó được phiên mã chính xác để khởi tạo với chuỗi nguyên mẫu chính xác.
Chỉnh sửa: Xin vui lòng đọc ý kiến. Hóa ra điều này chỉ hoạt động tốt trong V8 (Chrome / Node.JS) Ý định của tôi là cung cấp giải pháp trình duyệt chéo, sẽ hoạt động trong tất cả các trình duyệt và cung cấp dấu vết ngăn xếp ở đó có hỗ trợ.
Chỉnh sửa: Tôi đã tạo Cộng đồng Wiki này để cho phép chỉnh sửa nhiều hơn.
Giải pháp cho V8 (Chrome / Node.JS), hoạt động trong Firefox và có thể được sửa đổi để hoạt động chính xác trong IE. (xem cuối bài)
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
Error.call(this) // Does not seem necessary. Perhaps remove this line?
Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
this.message = message; // Used to set the message
}
Bài đăng gốc trên "Cho tôi xem mã!"
Phiên bản ngắn:
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
}
Tôi giữ this.constructor.prototype.__proto__ = Error.prototype
bên trong chức năng để giữ tất cả các mã với nhau. Nhưng bạn cũng có thể thay thế this.constructor
bằng UserError
và điều đó cho phép bạn di chuyển mã ra bên ngoài hàm, vì vậy nó chỉ được gọi một lần.
Nếu bạn đi theo con đường đó, hãy chắc chắn rằng bạn gọi đường dây đó trước lần ném đầu tiên UserError
.
Sự cảnh báo đó không áp dụng chức năng, bởi vì các chức năng được tạo ra trước tiên, bất kể thứ tự. Do đó, bạn có thể di chuyển chức năng đến cuối tập tin mà không gặp vấn đề gì.
tính tương thích của trình duyệt web
Hoạt động trong Firefox và Chrome (và Node.JS) và hoàn thành tất cả các lời hứa.
Internet Explorer thất bại trong các trường hợp sau
Lỗi không phải err.stack
bắt đầu, vì vậy "đó không phải là lỗi của tôi".
Error.captureStackTrace(this, this.constructor)
không tồn tại nên bạn cần phải làm một cái gì đó khác như
if(Error.captureStackTrace) // AKA if not IE
Error.captureStackTrace(this, this.constructor)
toString
chấm dứt tồn tại khi bạn phân lớp Error
. Vì vậy, bạn cũng cần phải thêm.
else
this.toString = function () { return this.name + ': ' + this.message }
IE sẽ không coi UserError
là một instanceof Error
trừ khi bạn chạy một thời gian sau trước khi bạnthrow UserError
UserError.prototype = Error.prototype
Error.call(this)
thực sự không làm gì cả vì nó trả về lỗi thay vì sửa đổi this
.
UserError.prototype = Error.prototype
là sai lệch. Điều này không làm kế thừa, điều này làm cho chúng cùng một lớp .
Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
được ưu tiên this.constructor.prototype.__proto__ = Error.prototype
, ít nhất là cho các trình duyệt hiện tại.
Để tránh lỗi nồi hơi cho mọi loại lỗi khác nhau, tôi đã kết hợp sự khôn ngoan của một số giải pháp vào một createErrorType
chức năng:
function createErrorType(name, init) {
function E(message) {
if (!Error.captureStackTrace)
this.stack = (new Error()).stack;
else
Error.captureStackTrace(this, this.constructor);
this.message = message;
init && init.apply(this, arguments);
}
E.prototype = new Error();
E.prototype.name = name;
E.prototype.constructor = E;
return E;
}
Sau đó, bạn có thể xác định các loại lỗi mới một cách dễ dàng như sau:
var NameError = createErrorType('NameError', function (name, invalidChar) {
this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});
var UnboundError = createErrorType('UnboundError', function (variableName) {
this.message = 'Variable ' + variableName + ' is not bound';
});
this.name = name;
?
name
đã được đặt trên nguyên mẫu, nên nó không còn cần thiết nữa. Tôi đã gỡ bỏ nó. Cảm ơn!
Năm 2018 , tôi nghĩ đây là cách tốt nhất; hỗ trợ IE9 + và các trình duyệt hiện đại.
CẬP NHẬT : Xem thử nghiệm này và repo để so sánh về các triển khai khác nhau.
function CustomError(message) {
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: 'CustomError'
});
Object.defineProperty(this, 'message', {
enumerable: false,
writable: true,
value: message
});
if (Error.hasOwnProperty('captureStackTrace')) { // V8
Error.captureStackTrace(this, CustomError);
} else {
Object.defineProperty(this, 'stack', {
enumerable: false,
writable: false,
value: (new Error(message)).stack
});
}
}
if (typeof Object.setPrototypeOf === 'function') {
Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
CustomError.prototype = Object.create(Error.prototype, {
constructor: { value: CustomError }
});
}
Cũng hãy cẩn thận rằng __proto__
tài sản bị phản đối được sử dụng rộng rãi trong các câu trả lời khác.
setPrototypeOf()
? Ít nhất là theo MDN, thường không khuyến khích sử dụng nó nếu bạn có thể hoàn thành điều tương tự bằng cách chỉ đặt thuộc .prototype
tính trên hàm tạo (như bạn đang làm trong else
khối cho lông mày không có setPrototypeOf
).
setPrototypeOf
. Nhưng nếu bạn vẫn cần nó (như OP yêu cầu), bạn nên sử dụng phương pháp tích hợp. Như MDN chỉ ra, đây được coi là cách thích hợp để đặt nguyên mẫu của một đối tượng. Nói cách khác, MDN nói không thay đổi nguyên mẫu (vì nó ảnh hưởng đến hiệu suất và tối ưu hóa) nhưng nếu bạn phải sử dụng setPrototypeOf
.
CustomError.prototype = Object.create(Error.prototype)
). Ngoài ra, Object.setPrototypeOf(CustomError, Error.prototype)
là thiết lập nguyên mẫu của chính hàm tạo thay vì chỉ định nguyên mẫu cho các phiên bản mới của CustomError
. Dù sao, vào năm 2016 tôi nghĩ rằng có thực sự là một cách tốt hơn để mở rộng lỗi, mặc dù tôi vẫn đang tìm hiểu làm thế nào để sử dụng nó cùng với Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/...
CustomError.prototype = Object.create(Error.prototype)
cũng đang thay đổi nguyên mẫu. Bạn phải thay đổi nó vì không có logic mở rộng / kế thừa tích hợp trong ES5. Tôi chắc chắn rằng plugin babel mà bạn đề cập có tác dụng tương tự.
Object.setPrototypeOf
không có ý nghĩa ở đây, ít nhất là theo cách bạn đang sử dụng: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Có lẽ bạn muốn viết Object.setPrototypeOf(CustomError.prototype, Error.prototype)
- điều đó sẽ có ý nghĩa hơn một chút (mặc dù vẫn không mang lại lợi ích gì so với việc chỉ đơn giản là thiết lập CustomError.prototype
).
Để hoàn thiện - chỉ vì không có câu trả lời nào trước đây đề cập đến phương pháp này - nếu bạn đang làm việc với Node.js và không phải quan tâm đến khả năng tương thích của trình duyệt, hiệu quả mong muốn khá dễ dàng đạt được với tích hợp sẵn inherits
của util
mô-đun ( tài liệu chính thức ở đây ).
Ví dụ: giả sử bạn muốn tạo một lớp lỗi tùy chỉnh lấy mã lỗi làm đối số thứ nhất và thông báo lỗi làm đối số thứ hai:
tệp custom-error.js :
'use strict';
var util = require('util');
function CustomError(code, message) {
Error.captureStackTrace(this, CustomError);
this.name = CustomError.name;
this.code = code;
this.message = message;
}
util.inherits(CustomError, Error);
module.exports = CustomError;
Bây giờ bạn có thể khởi tạo và vượt qua / ném CustomError
:
var CustomError = require('./path/to/custom-error');
// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));
// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');
Lưu ý rằng, với đoạn mã này, theo dõi ngăn xếp sẽ có tên và dòng tệp chính xác và trường hợp lỗi sẽ có tên chính xác!
Điều này xảy ra do việc sử dụng captureStackTrace
phương thức, tạo ra một thuộc stack
tính trên đối tượng đích (trong trường hợp này CustomError
là việc được khởi tạo). Để biết thêm chi tiết về cách thức hoạt động, kiểm tra tài liệu ở đây .
this.message = this.message;
Điều này có sai hay vẫn còn những điều điên rồ mà tôi không biết về JS?
Câu trả lời được đánh giá cao của Crescent Fresh là sai lệch. Mặc dù các cảnh báo của anh ta không hợp lệ, nhưng có những hạn chế khác mà anh ta không giải quyết.
Đầu tiên, lý do trong đoạn "Caveats:" của Crescent không có ý nghĩa. Giải thích ngụ ý rằng việc mã hóa "một bó if (lỗi của MyError) khác ..." bằng cách nào đó là gánh nặng hoặc dài dòng so với nhiều câu lệnh bắt. Nhiều câu lệnh thể hiện trong một khối lệnh bắt đơn giản như nhiều câu lệnh bắt - mã sạch và súc tích mà không có bất kỳ thủ thuật nào. Đây là một cách tuyệt vời để mô phỏng xử lý lỗi cụ thể theo kiểu phụ có thể ném được của Java.
WRT "xuất hiện thuộc tính thông báo của lớp con không được đặt", đó không phải là trường hợp nếu bạn sử dụng lớp con Lỗi được xây dựng đúng. Để tạo lớp con ErrorX Error của riêng bạn, chỉ cần sao chép khối mã bắt đầu bằng "var MyError =", thay đổi một từ "MyError" thành "ErrorX". (Nếu bạn muốn thêm các phương thức tùy chỉnh vào lớp con của mình, hãy làm theo văn bản mẫu).
Hạn chế thực sự và đáng kể của phân lớp lỗi JavaScript là đối với các triển khai hoặc trình gỡ lỗi JavaScript theo dõi và báo cáo về dấu vết ngăn xếp và vị trí tức thời, như FireFox, một vị trí trong triển khai lớp con Lỗi của riêng bạn sẽ được ghi lại làm điểm khởi tạo của lớp, trong khi nếu bạn sử dụng Lỗi trực tiếp, đó sẽ là vị trí bạn chạy "Lỗi mới (...)"). Người dùng IE có thể sẽ không bao giờ nhận thấy, nhưng người dùng Fire Bug trên FF sẽ thấy các giá trị tên tệp và số dòng vô dụng được báo cáo cùng với các Lỗi này và sẽ phải đi sâu vào dấu vết ngăn xếp đến phần tử # 1 để tìm vị trí khởi tạo thực.
Crescent Fresh's
đã bị xóa!
Làm thế nào về giải pháp này?
Thay vì ném Lỗi tùy chỉnh của bạn bằng cách sử dụng:
throw new MyError("Oops!");
Bạn sẽ bao bọc đối tượng Error (giống như một Trình trang trí):
throw new MyError(Error("Oops!"));
Điều này đảm bảo tất cả các thuộc tính đều chính xác, chẳng hạn như stack, fileName lineNumber, et cetera.
Tất cả những gì bạn phải làm sau đó là sao chép qua các thuộc tính hoặc xác định getters cho chúng. Dưới đây là một ví dụ sử dụng getters (IE9):
function MyError(wrapped)
{
this.wrapped = wrapped;
this.wrapped.name = 'MyError';
}
function wrap(attr)
{
Object.defineProperty(MyError.prototype, attr, {
get: function()
{
return this.wrapped[attr];
}
});
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');
MyError.prototype.toString = function()
{
return this.wrapped.toString();
};
new MyErr (arg1, arg2, new Error())
và trong hàm tạo MyErr, chúng tôi sử dụng Object.assign
để gán các thuộc tính của đối số cuối cùng chothis
Như một số người đã nói, nó khá dễ dàng với ES6:
class CustomError extends Error { }
Vì vậy, tôi đã thử trong ứng dụng của mình, (Angular, Typecript) và nó không hoạt động. Sau một thời gian tôi đã thấy rằng vấn đề đến từ Bản in: O
Xem https://github.com/Microsoft/TypeScript/issues/13965
Điều đó rất đáng lo ngại vì nếu bạn làm:
class CustomError extends Error {}
try {
throw new CustomError()
} catch(e) {
if (e instanceof CustomError) {
console.log('Custom error');
} else {
console.log('Basic error');
}
}
Trong nút hoặc trực tiếp vào trình duyệt của bạn, nó sẽ hiển thị: Custom error
Hãy thử chạy nó với Bản in trong dự án của bạn trên sân chơi Bản mô tả, nó sẽ hiển thị Basic error
...
Giải pháp là làm như sau:
class CustomError extends Error {
// we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
// otherwise we cannot use instanceof later to catch a given type
public __proto__: Error;
constructor(message?: string) {
const trueProto = new.target.prototype;
super(message);
this.__proto__ = trueProto;
}
}
Giải pháp của tôi đơn giản hơn các câu trả lời khác được cung cấp và không có nhược điểm.
Nó bảo tồn chuỗi nguyên mẫu Lỗi và tất cả các thuộc tính trên Lỗi mà không cần kiến thức cụ thể về chúng. Nó đã được thử nghiệm trong Chrome, Firefox, Node và IE11.
Giới hạn duy nhất là một mục nhập thêm ở đầu ngăn xếp cuộc gọi. Nhưng điều đó dễ dàng bị bỏ qua.
Đây là một ví dụ với hai tham số tùy chỉnh:
function CustomError(message, param1, param2) {
var err = new Error(message);
Object.setPrototypeOf(err, CustomError.prototype);
err.param1 = param1;
err.param2 = param2;
return err;
}
CustomError.prototype = Object.create(
Error.prototype,
{name: {value: 'CustomError', enumerable: false}}
);
Cách sử dụng ví dụ:
try {
throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
console.log(ex.name); //CustomError
console.log(ex.message); //Something Unexpected Happened!
console.log(ex.param1); //1234
console.log(ex.param2); //neat
console.log(ex.stack); //stacktrace
console.log(ex instanceof Error); //true
console.log(ex instanceof CustomError); //true
}
Đối với các môi trường yêu cầu polyfil của setPrototypeOf:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
throw CustomError('err')
thay vìthrow new CustomError('err')
Trong ví dụ trên Error.apply
(cũng Error.call
) không làm gì cho tôi (Firefox 3.6 / Chrome 5). Một cách giải quyết tôi sử dụng là:
function MyError(message, fileName, lineNumber) {
var err = new Error();
if (err.stack) {
// remove one stack level:
if (typeof(Components) != 'undefined') {
// Mozilla:
this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
}
else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
// Google Chrome/Node.js:
this.stack = err.stack.replace(/\n[^\n]*/,'');
}
else {
this.stack = err.stack;
}
}
this.message = message === undefined ? err.message : message;
this.fileName = fileName === undefined ? err.fileName : fileName;
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';
Trong Node như những người khác đã nói, thật đơn giản:
class DumbError extends Error {
constructor(foo = 'bar', ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DumbError);
}
this.name = 'DumbError';
this.foo = foo;
this.date = new Date();
}
}
try {
let x = 3;
if (x < 10) {
throw new DumbError();
}
} catch (error) {
console.log(error);
}
Tôi chỉ muốn thêm vào những gì người khác đã nêu:
Để đảm bảo rằng lớp lỗi tùy chỉnh hiển thị đúng trong theo dõi ngăn xếp, bạn cần đặt thuộc tính tên nguyên mẫu của lớp lỗi tùy chỉnh thành thuộc tính tên của lớp lỗi tùy chỉnh. Đó là thứ tôi nghĩ:
CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';
Vì vậy, ví dụ đầy đủ sẽ là:
var CustomError = function(message) {
var err = new Error(message);
err.name = 'CustomError';
this.name = err.name;
this.message = err.message;
//check if there is a stack property supported in browser
if (err.stack) {
this.stack = err.stack;
}
//we should define how our toString function works as this will be used internally
//by the browser's stack trace generation function
this.toString = function() {
return this.name + ': ' + this.message;
};
};
CustomError.prototype = new Error();
CustomError.prototype.name = 'CustomError';
Khi tất cả được nói và thực hiện, bạn ném ngoại lệ mới của mình và nó trông như thế này (tôi đã lười biếng thử điều này trong các công cụ phát triển chrome):
CustomError: Stuff Happened. GASP!
at Error.CustomError (<anonymous>:3:19)
at <anonymous>:2:7
at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
at Object.InjectedScript.evaluate (<anonymous>:481:21)
2 xu của tôi:
a) Bởi vì truy cập vào Error.stack
tài sản (như trong một số câu trả lời) có một hình phạt hiệu suất lớn.
b) Vì nó chỉ là một dòng.
c) Bởi vì giải pháp tại https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error dường như không lưu giữ thông tin ngăn xếp.
//MyError class constructor
function MyError(msg){
this.__proto__.__proto__ = Error.apply(null, arguments);
};
ví dụ sử dụng
http://jsfiddle.net/luciotato/xXyeB/
this.__proto__.__proto__
là MyError.prototype.__proto__
, do đó, nó đang đặt __proto__
TẤT CẢ CÁC TÀI KHOẢN của MyError thành một Lỗi cụ thể mới được tạo. Nó giữ các thuộc tính và phương thức của lớp MyError và cũng đặt các thuộc tính Lỗi mới (bao gồm .stack) trong __proto__
chuỗi.
Bạn không thể có nhiều hơn một phiên bản MyError với thông tin ngăn xếp hữu ích.
Không sử dụng giải pháp này nếu bạn không hiểu đầy đủ những gì this.__proto__.__proto__=
.
Vì Ngoại lệ JavaScript khó phân loại, nên tôi không phân loại. Tôi chỉ tạo một lớp Exception mới và sử dụng một Lỗi bên trong nó. Tôi thay đổi thuộc tính Error.name để nó trông giống như ngoại lệ tùy chỉnh của tôi trên bảng điều khiển:
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
Ngoại lệ mới ở trên có thể được ném giống như một Lỗi thông thường và nó sẽ hoạt động như mong đợi, ví dụ:
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
Hãy cẩn thận: theo dõi ngăn xếp không hoàn hảo, vì nó sẽ đưa bạn đến nơi Lỗi mới được tạo và không phải nơi bạn ném. Đây không phải là một vấn đề lớn trên Chrome vì nó cung cấp cho bạn một dấu vết ngăn xếp đầy đủ trực tiếp trong bảng điều khiển. Nhưng nó có vấn đề hơn trên Firefox chẳng hạn.
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
sau đó Promise.reject(m)
. Nó không cần thiết, nhưng mã dễ đọc hơn.
Như đã chỉ ra trong câu trả lời của Mohsen, trong ES6, có thể mở rộng lỗi bằng cách sử dụng các lớp. Việc này dễ dàng hơn rất nhiều và hành vi của họ phù hợp hơn với các lỗi gốc ... nhưng thật không may, việc sử dụng điều này trong trình duyệt không phải là vấn đề đơn giản nếu bạn cần hỗ trợ các trình duyệt trước ES6. Xem bên dưới để biết một số lưu ý về cách thực hiện điều đó, nhưng trong lúc này tôi đề xuất một cách tiếp cận tương đối đơn giản kết hợp một số gợi ý tốt nhất từ các câu trả lời khác:
function CustomError(message) {
//This is for future compatibility with the ES6 version, which
//would display a similar message if invoked without the
//`new` operator.
if (!(this instanceof CustomError)) {
throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
}
this.message = message;
//Stack trace in V8
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';
Trong ES6, nó đơn giản như:
class CustomError extends Error {}
... và bạn có thể phát hiện hỗ trợ cho các lớp ES6 try {eval('class X{}')
, nhưng bạn sẽ gặp lỗi cú pháp nếu bạn cố gắng đưa phiên bản ES6 vào tập lệnh được tải bởi các trình duyệt cũ hơn. Vì vậy, cách duy nhất để hỗ trợ tất cả các trình duyệt là tự động tải một tập lệnh riêng biệt (ví dụ: thông qua AJAX hoặceval()
) cho các trình duyệt hỗ trợ ES6. Một biến chứng nữa làeval()
là không được hỗ trợ trong tất cả các môi trường (do Chính sách bảo mật nội dung), có thể hoặc không thể xem xét cho dự án của bạn.
Vì vậy, bây giờ, cách tiếp cận đầu tiên ở trên hoặc đơn giản là sử dụng Error
trực tiếp mà không cố gắng mở rộng dường như là cách tốt nhất có thể được thực hiện đối với mã cần hỗ trợ các trình duyệt không phải ES6.
Có một cách tiếp cận khác mà một số người có thể muốn xem xét, đó là sử dụng Object.setPrototypeOf()
ở nơi có sẵn để tạo một đối tượng lỗi, đó là một thể hiện của loại lỗi tùy chỉnh của bạn nhưng trông giống và lỗi giống như lỗi trong bảng điều khiển (nhờ câu trả lời của Ben cho các khuyến nghị). Đây là cách tiếp cận của tôi: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Nhưng cho rằng một ngày nào đó chúng ta sẽ có thể chỉ sử dụng ES6, cá nhân tôi không chắc sự phức tạp của phương pháp đó có đáng không.
Cách để thực hiện quyền này là trả về kết quả của ứng dụng từ hàm tạo, cũng như đặt nguyên mẫu theo cách javascripty phức tạp thông thường:
function MyError() {
var tmp = Error.apply(this, arguments);
tmp.name = this.name = 'MyError'
this.stack = tmp.stack
this.message = tmp.message
return this
}
var IntermediateInheritor = function() {}
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor()
var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error) // true
console.log(myError instanceof MyError) // true
console.log(myError.toString()) // MyError: message
console.log(myError.stack) // MyError: message \n
// <stack trace ...>
Vấn đề duy nhất với cách làm này vào thời điểm này (tôi đã lặp lại một chút) là
stack
và message
không bao gồm trongMyError
vàVấn đề đầu tiên có thể được khắc phục bằng cách lặp qua tất cả các thuộc tính không thể đếm được của lỗi bằng cách sử dụng mẹo trong câu trả lời này: Có thể lấy tên thuộc tính được thừa kế không thể đếm được của một đối tượng không? , nhưng điều này không được hỗ trợ bởi tức là <9. Vấn đề thứ hai có thể được giải quyết bằng cách xé dòng đó trong theo dõi ngăn xếp, nhưng tôi không chắc làm thế nào để làm điều đó một cách an toàn (có thể chỉ xóa dòng thứ hai của e.stack.toString () ??).
Đoạn trích cho thấy tất cả.
function add(x, y) {
if (x && y) {
return x + y;
} else {
/**
*
* the error thrown will be instanceof Error class and InvalidArgsError also
*/
throw new InvalidArgsError();
// throw new Invalid_Args_Error();
}
}
// Declare custom error using using Class
class Invalid_Args_Error extends Error {
constructor() {
super("Invalid arguments");
Error.captureStackTrace(this);
}
}
// Declare custom error using Function
function InvalidArgsError(message) {
this.message = `Invalid arguments`;
Error.captureStackTrace(this);
}
// does the same magic as extends keyword
Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);
try{
add(2)
}catch(e){
// true
if(e instanceof Error){
console.log(e)
}
// true
if(e instanceof InvalidArgsError){
console.log(e)
}
}
Tôi sẽ lùi một bước và xem xét lý do tại sao bạn muốn làm điều đó? Tôi nghĩ rằng vấn đề là để đối phó với các lỗi khác nhau khác nhau.
Ví dụ: trong Python, bạn có thể hạn chế câu lệnh bắt chỉ bắt MyValidationError
và có lẽ bạn muốn có thể làm điều gì đó tương tự trong javascript.
catch (MyValidationError e) {
....
}
Bạn không thể làm điều này trong javascript. Sẽ chỉ có một khối bắt. Bạn nên sử dụng một câu lệnh if về lỗi để xác định loại của nó.
catch(e) {
if(isMyValidationError(e)) {
...
} else {
// maybe rethrow?
throw e;
}
}
Tôi nghĩ rằng thay vào đó tôi sẽ ném một đối tượng thô với một loại, thông điệp và bất kỳ thuộc tính nào khác mà bạn thấy phù hợp.
throw { type: "validation", message: "Invalid timestamp" }
Và khi bạn bắt lỗi:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
error.stack
tiêu chuẩn cụ sẽ không làm việc với nó, vv vv Một cách tốt hơn sẽ có thêm thuộc tính cho một trường hợp lỗi, ví dụ nhưvar e = new Error(); e.type = "validation"; ...
Điều này dựa trên câu trả lời của George Bailey , nhưng mở rộng và đơn giản hóa ý tưởng ban đầu. Nó được viết bằng CoffeeScript, nhưng rất dễ chuyển đổi sang JavaScript. Ý tưởng là mở rộng lỗi tùy chỉnh của Bailey với một trình trang trí bao bọc nó, cho phép bạn tạo ra các lỗi tùy chỉnh mới một cách dễ dàng.
Lưu ý: Điều này sẽ chỉ hoạt động trong V8. Không có hỗ trợ cho Error.captureStackTrace
các môi trường khác.
Trình trang trí lấy tên cho loại lỗi và trả về một hàm nhận thông báo lỗi và kèm theo tên lỗi.
CoreError = (@message) ->
@constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace @, @constructor
@name = @constructor.name
BaseError = (type) ->
(message) -> new CoreError "#{ type }Error: #{ message }"
Bây giờ thật đơn giản để tạo các loại lỗi mới.
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
Để giải trí, giờ đây bạn có thể định nghĩa một hàm ném SignatureError
nếu nó được gọi với quá nhiều đối số.
f = -> throw SignatureError "too many args" if arguments.length
Điều này đã được thử nghiệm khá tốt và dường như hoạt động hoàn hảo trên V8, duy trì truy nguyên, vị trí, v.v.
Lưu ý: Sử dụng new
là tùy chọn khi xây dựng một lỗi tùy chỉnh.
nếu bạn không quan tâm đến các màn trình diễn cho lỗi thì đây là lỗi nhỏ nhất bạn có thể làm
Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
const error = new Error(message)
Object.setPrototypeOf(error, MyError.prototype);
return error
}
bạn có thể sử dụng nó mà không cần mới MyError (tin nhắn)
Bằng cách thay đổi nguyên mẫu sau khi lỗi hàm tạo được gọi, chúng ta không phải đặt lệnh gọi và thông báo
Mohsen có một câu trả lời tuyệt vời ở trên trong ES6, đặt tên, nhưng nếu bạn đang sử dụng TypeScript hoặc nếu bạn đang sống trong tương lai, nơi hy vọng đề xuất này cho các trường lớp công khai và riêng tư đã chuyển qua giai đoạn 3 như một đề xuất và thực hiện nó vào giai đoạn 4 như là một phần của ECMAScript / JavaScript, sau đó bạn có thể muốn biết điều này sau đó chỉ ngắn hơn một chút. Giai đoạn 3 là nơi các trình duyệt bắt đầu triển khai các tính năng, vì vậy nếu trình duyệt của bạn hỗ trợ thì mã bên dưới có thể hoạt động. (Được thử nghiệm trong trình duyệt Edge mới v81, nó dường như hoạt động tốt). Được cảnh báo mặc dù hiện tại đây là một tính năng không ổn định và nên được sử dụng thận trọng và bạn phải luôn kiểm tra hỗ trợ trình duyệt về các tính năng không ổn định. Bài đăng này chủ yếu dành cho những người ở tương lai khi các trình duyệt có thể hỗ trợ điều này. Để kiểm tra hỗ trợ kiểm tra MDNvàTôi có thể sử dụng . Hiện tại, nó đã nhận được 66% hỗ trợ trên thị trường trình duyệt đang đến đó nhưng không tuyệt vời lắm nên nếu bạn thực sự muốn sử dụng nó ngay bây giờ và không muốn chờ đợi, hãy sử dụng một bộ chuyển mã như Babel hoặc một cái gì đó như TypeScript .
class EOFError extends Error {
name="EOFError"
}
throw new EOFError("Oops errored");
So sánh lỗi này với một lỗi không tên mà khi ném sẽ không ghi tên của nó.
class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");
this.name = 'MyError'
bên ngoài của chức năng và thay đổi nó thànhMyError.prototype.name = 'MyError'
.