Cách tốt để mở rộng Lỗi trong JavaScript là gì?


369

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:


218

Đối tượng lỗi trường tiêu chuẩn duy nhất có là thuộc messagetí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 stacktài sản, nhưng fileNamelineNumberlà 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 stacktính, nhưng bộ throwtừ khóa sourceURLlinethuộ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 .


19
Bạn có thể di chuyển this.name = 'MyError' bên ngoài của chức năng và thay đổi nó thành MyError.prototype.name = 'MyError'.
Daniel Beardsley

36
Đây là câu trả lời đúng duy nhất ở đây, mặc dù là vấn đề về phong cách, có lẽ tôi sẽ viết nó như thế này. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos

11
Tôi cũng muốn thêm MyError.prototype.constructor = MyError.
Bharat Khatri

3
trong ES6 Error.call (cái này, tin nhắn); Nên khởi tạo this, phải không?
4esn0k

4
MyError.prototype = Object.create (Error.prototype);
Robin như con chim

170

Trong ES6:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
}

nguồn


53
Đáng nói là điều này không hoạt động nếu bạn đang sử dụng các tính năng ES6 thông qua bộ chuyển mã, chẳng hạn như Babel, vì các lớp con phải mở rộng một lớp.
aaaidan

6
Nếu bạn đang sử dụng babel và đang ở nút> 5.x, bạn không nên sử dụng cài đặt sẵn es2015 nhưng npmjs.com/package/babel-preset-node5 sẽ cho phép bạn sử dụng es6 bản gốc kéo dài thêm
Ace

2
Đây là cách tốt nhất để làm điều đó khi có thể. Lỗi tùy chỉnh hoạt động giống như lỗi thông thường trong cả Chrome và Firefox (và có thể cả các trình duyệt khác nữa).
Matt Browne

2
Đối với trình duyệt, lưu ý rằng bạn có thể phát hiện hỗ trợ cho các lớp khi chạy và quay lại phiên bản không phải là lớp tương ứng. Mã phát hiện:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne

31
Để dễ bảo trì , sử dụng this.name = this.constructor.name;thay thế.
Toàn cảnh

53

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, DOMhoặ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ố instanceofnhư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ố instanceofmà 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

1
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 constvà nó không xử lý các đối số tùy chỉnh.
Mất lòng

Câu trả lời rất đầy đủ.
Daniele Orlando

Điều này thực sự cung cấp giải pháp toàn diện nhất và giải thích tại sao các phần khác nhau là cần thiết. Cảm ơn rất nhiều!
AndrewSouthpaw

Điều này giúp tôi giải quyết vấn đề với việc kế thừa từ "Lỗi". Đó là một cơn ác mộng hai giờ!
Iulian Pinzaru

2
Đáng lưu ý rằng vấn đề với console.log(new CustomError('test') instanceof CustomError);// falselà đú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.
Nobita

44

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.prototypebê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.constructorbằng UserErrorvà đ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.stackbắ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)
  • toStringchấ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 UserErrorlà một instanceof Errortrừ khi bạn chạy một thời gian sau trước khi bạnthrow UserError

    UserError.prototype = Error.prototype

16
Tôi không nghĩ Firefox thực sự có captStackTrace. Đó là một tiện ích mở rộng V8 và không được xác định trong Firefox đối với tôi, tôi cũng không thể tìm thấy bất kỳ tài liệu tham khảo nào trên web để Firefox hỗ trợ. (Cảm ơn mặc dù!)
Geoff

5
Error.call(this)thực sự không làm gì cả vì nó trả về lỗi thay vì sửa đổi this.
kybernetikos

1
Hoạt động tuyệt vời cho Node.js
Rudolf Meijering

1
UserError.prototype = Error.prototypelà 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 .
Halcyon

1
Tôi tin rằng 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.
ChrisV

29

Để 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  createErrorTypechứ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';
});

Có một lý do bạn vẫn cần dòng this.name = name;?
Peter Tseng

@PeterTseng Vì 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!
Ruben Verborgh

27

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àyrepo để 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.


1
Tại sao bạn đang sử dụng 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 .prototypetính trên hàm tạo (như bạn đang làm trong elsekhối cho lông mày không có setPrototypeOf).
Matt Browne

Thay đổi nguyên mẫu của một đối tượng là không khuyến khích tất cả cùng nhau, không 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.
Onur Yıldırım

Quan điểm của tôi là tôi không nghĩ bạn thực sự cần thay đổi nguyên mẫu ở đây. Bạn chỉ có thể sử dụng dòng của bạn ở dưới cùng ( 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/...
Matt Browne

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ự.
Onur Yıldırım

1
Tôi đã tạo ra một ý chính chứng minh tại sao việc sử dụng Object.setPrototypeOfkhô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).
Matt Browne

19

Để 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 inheritscủa utilmô-đ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 captureStackTracephương thức, tạo ra một thuộc stacktính trên đối tượng đích (trong trường hợp này CustomErrorlà 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 .


1
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?
Alex

1
Này @Alex, bạn hoàn toàn đúng! Bây giờ nó đã được sửa. Cảm ơn!
Victor Schröder

18

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.


Tôi đã hiểu đúng chưa - rằng nếu bạn không phân lớp và sử dụng trực tiếp Lỗi mới (...), thì tên và dòng tệp đang được báo cáo đúng? Và về cơ bản, bạn nói rằng trên thực tế (thực tế và không chỉ là lỗi phân loại gợi cảm hay trang trí), không có ý nghĩa gì?
jayarjo

6
Câu trả lời này là một số điều khó hiểu như Crescent Fresh'sđã bị xóa!
Peter

đây vẫn là trường hợp? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ Số dòng là 2 không phải nơi mới được gọi
Muhammad Umer

13

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();
};

1
Tôi đã phát hành giải pháp này dưới dạng gói npm
JoWie

Giải pháp cực kỳ thanh lịch, cảm ơn đã chia sẻ! Một biến thể: 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
Peeyush Kushwaha

Tôi thích điều này. Bạn bỏ qua một giới hạn bằng cách sử dụng đóng gói thay vì thừa kế.
Jenny O'Reilly

13

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;
  }
}

10

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;
};

1
như được ghi lại trong câu trả lời của tôi, giải pháp này có thể gây ra sự cố trong Firefox hoặc các trình duyệt khác chỉ ghi lại dòng đầu tiên của dấu vết ngăn xếp trong bảng điều khiển
Jonathan Benn

Đây là câu trả lời duy nhất tôi thấy rằng hoạt động tốt với ES5 (sử dụng các lớp ES6 cũng hoạt động tốt). Lỗi hiển thị độc đáo hơn nhiều trong Chromium DevTools so với các câu trả lời khác.
Ben Gubler

Lưu ý: nếu bạn đang sử dụng giải pháp này với TypeScript, bạn phải chạy throw CustomError('err')thay vìthrow new CustomError('err')
Ben Gubler

8

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';

5

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);
}

3

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)

5
Điều này có ghi đè lên thuộc tính tên cho TẤT CẢ các trường hợp Lỗi không?
panzi

@panzi bạn đúng rồi. Tôi đã sửa lỗi nhỏ của tôi. Cảm ơn cho những người đứng đầu lên!
Gautham C.

3

2 xu của tôi:

Tại sao câu trả lời khác?

a) Bởi vì truy cập vào Error.stacktà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/

Nó làm gì?

this.__proto__.__proto__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.

Vấn đề rõ ràng:

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__=.


2

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.


Điều này thất bại trong trường hợpm = new InvalidInputError(); dontThrowMeYet(m);
Eric

@Eric Tôi đồng ý, nhưng đây có vẻ là một hạn chế khá nhỏ. Tôi chưa bao giờ cần khởi tạo một đối tượng ngoại lệ trước thời hạn (ngoại trừ việc sử dụng lập trình meta như mẫu mã của tôi ở trên). Đây thực sự là một vấn đề cho bạn?
Jonathan Benn

Vâng, hành vi dường như giống nhau, vì vậy tôi sẽ thay đổi câu trả lời của mình. Tôi không hài lòng 100% với dấu vết ngăn xếp, đưa bạn đến dòng "lỗi var" trên Firefox và Chrome
Jonathan Benn

1
@JonathanBenn Tôi đến bữa tiệc rất muộn, nên có lẽ bạn đã chọn cái này. Tôi thường khởi tạo một đối tượng ngoại lệ khi tôi sử dụng lập trình và lời hứa không đồng bộ. Theo tên của @ Eric, tôi thường sử dụng m = new ...sau đó Promise.reject(m). Nó không cần thiết, nhưng mã dễ đọc hơn.
BaldEagle

1
@JonathanBenn: (he he) vào ngày 14 tháng 10, bạn dường như nghĩ ngay lập tức một đối tượng ngoại lệ trước khi ném nó sẽ rất hiếm. Tôi đã đưa ra một ví dụ về một lần tôi làm điều đó. Tôi sẽ không nói nó phổ biến, nhưng nó có ích khi tôi muốn nó. Và, mã của tôi dễ đọc hơn vì việc khởi tạo là tất cả trên một dòng và từ chối tất cả trên một dòng khác. Tôi hy vọng điều đó làm điều đó!
BaldEagle

2

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.


1

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à

  • các thuộc tính khác stackmessagekhông bao gồm trongMyError
  • stacktrace có một dòng bổ sung không thực sự cần thiết.

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 () ??).


Tôi đã tạo một mô-đun có thể mở rộng hầu hết các đối tượng javascript cũ thông thường, bao gồm cả Lỗi. Nó khá trưởng thành vào thời điểm này github.com/fresheneesz/proto
BT

1

Đ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)
      }
    }

0

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 MyValidationErrorvà 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
}

1
Ném một vật không phải là một ý tưởng tuyệt vời. Bạn không có error.stacktiê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"; ...
timruffles

0

Tùy chỉnh trang trí lỗi

Đ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.captureStackTracecác môi trường khác.

Định nghĩa

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 }"

Sử dụng

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 SignatureErrornế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 newlà tùy chọn khi xây dựng một lỗi tùy chỉnh.


0

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


0

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 MDNTô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");

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.