Lỗi mở rộng trong Javascript với cú pháp ES6 & Babel


132

Tôi đang cố gắng mở rộng Lỗi với ES6 và Babel. Nó không hoạt động.

class MyError extends Error {
  constructor(m) {
    super(m);
  }
}

var error = new Error("ll");
var myerror = new MyError("ll");
console.log(error.message) //shows up correctly
console.log(myerror.message) //shows empty string

Đối tượng Error không bao giờ có được bộ thông báo đúng.

Hãy thử trong Babel REPL .

Bây giờ tôi đã thấy một vài giải pháp về SO ( ví dụ ở đây ), nhưng tất cả chúng đều có vẻ rất không ES6-y. Làm thế nào để làm điều đó một cách tốt đẹp, ES6? (Đó là làm việc ở Babel)


2
Theo liên kết của bạn đến Babel REPL dường như cho thấy rằng nó hoạt động chính xác ngay bây giờ. Tôi đoán đó là một lỗi trong Babel đã được sửa chữa.
kybernetikos

Câu trả lời:


188

Dựa trên câu trả lời của Karel Bílek, tôi sẽ thực hiện một thay đổi nhỏ đối với constructor:

class ExtendableError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor);
    } else { 
      this.stack = (new Error(message)).stack; 
    }
  }
}    

// now I can extend

class MyError extends ExtendableError {}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

Điều này sẽ in MyErrortrong ngăn xếp, và không phải là chung chung Error.

Nó cũng sẽ thêm thông báo lỗi vào theo dõi ngăn xếp - thứ bị thiếu trong ví dụ của Karel.

Nó cũng sẽ sử dụng captureStackTracenếu nó có sẵn.

Với Babel 6, bạn cần chuyển đổi-dựng sẵn ( npm ) để làm việc này.


1
@MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . Tôi cho rằng sẽ tốt hơn nếu sử dụng chức năng này nếu nó khả dụng, vì nó cung cấp ngăn xếp cuộc gọi 'bản địa' hơn và in tên của đối tượng lỗi. Tất nhiên, nếu bạn chỉ sử dụng cái này ở phía máy chủ (Node), thì đó cũng không phải là vấn đề.
Lee Benson

4
@MichaelYounkin Tôi không nghĩ rằng điều này xứng đáng với một downvote. OP đã nói về việc mở rộng lỗi trong ES6. Theo logic đó, gần như toàn bộ ES6 bị thiếu từ ít nhất một trình duyệt. Giải pháp của tôi (với kiểm tra func được thêm vào) cung cấp phạm vi bảo hiểm gốc trong trình duyệt được sử dụng rộng rãi nhất, dự phòng cho nhau và bảo hiểm 100% trong Node.js. Tôi đồng ý rằng nếu bạn có lỗi lớp tên liên tục thì this.stack = (new Error(message)).stackbạn sẽ nhận được điều đó ... nhưng trong thực tế, đây có lẽ không phải là một vấn đề lớn.
Lee Benson

6
Điều này không hoạt động trong Babel 6:new MyError('foo') instanceof MyError === false
Sukima

5
Mã này được biên dịch trước với babel dưới dạng mô-đun NPM: extendable-error-class npmjs.com/package/extendable-error- class thuận tiện để tránh sự phụ thuộc vào babel-plugin-Transform-
buildin

3
this.message = message;là dư thừa vớisuper(message);
mathieug

39

Kết hợp câu trả lời này , câu trả lời nàymã này , tôi đã tạo ra lớp "người trợ giúp" nhỏ này, điều đó dường như hoạt động tốt.

class ExtendableError extends Error {
  constructor(message) {
    super();
    this.message = message; 
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}    

// now I can extend

class MyError extends ExtendableError {
  constructor(m) {   
    super(m);
  }
}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

Thử trong REPL


1
this.stack = (new Error(message)).stack;- nếu không thì tin nhắn bị thiếu trong stacktrace
Lee Benson

3
Tôi nghi ngờ rằng điều này không hoạt động khi cần thiết, bởi vì nếu bạn làm: console.log (myerror instanceof ExtendableError); nó vẫn nói sai ..
Mauno Vähä

4
cùng một vấn đề, sử dụng instanceof CustomError không hoạt động, điểm mở rộng là gì nếu bạn không thể sử dụng instanceof.
GRE

Nó có thể được cải thiện khi thêm hàm tạo messagetrong ngăn xếp Lỗi, vì vậy nó hiển thị đúng thông báo ở đầu ngăn xếp khi ném:this.stack = (new Error(message)).stack;
Sebastien

1
myerror.namebây giờ trả về "Lỗi". Không chắc điều này có liên quan đến các phiên bản sau của babel không. Câu trả lời của @ sukima bên dưới
Eric H.

27

Để cuối cùng đặt điều này để nghỉ ngơi. Trong Babel 6 nó là rõ ràng rằng các nhà phát triển không hỗ trợ kéo dài từ xây dựng trong. Mặc dù thủ thuật này sẽ không giúp đỡ với điều thích Map, Setvv nó không làm việc cho Error. Điều này rất quan trọng vì một trong những ý tưởng cốt lõi của ngôn ngữ có thể đưa ra một ngoại lệ là cho phép các lỗi tùy chỉnh. Điều này đôi khi rất quan trọng vì Lời hứa trở nên hữu ích hơn vì chúng được thiết kế để từ chối Lỗi .

Sự thật đáng buồn là bạn vẫn cần thực hiện theo cách cũ trong ES2015.

Ví dụ trong REPL Babel

Mẫu lỗi tùy chỉnh

class MyError {
  constructor(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
}
MyError.prototype = Object.create(Error.prototype);

Mặt khác, có một plugin cho Babel 6 để cho phép điều này.

https://www.npmjs.com/package/babel-plugin-transform-builtin-extend

Cập nhật: (kể từ 2016-09-29) Sau một số thử nghiệm, có vẻ như babel.io không giải thích đúng cho tất cả các xác nhận (mở rộng từ Lỗi mở rộng tùy chỉnh). Nhưng trong Ember.JS kéo dài Lỗi hoạt động như mong đợi: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce


vâng, với plugin babel được liên kết, nó hoạt động chính xác với câu trả lời được chấp nhận. (Tuy nhiên, tệp kết quả không hoạt động trong Node, vì rõ ràng nó không có Reflect)
Karel Bílek

Cũng giống như một sự tò mò, nếu thông số kỹ thuật ES2016 nói rằng các nội dung có thể mở rộng thì tại sao các vms như v8 và Bab5 của es5 lại chống lại nó? Có phải là một kỳ vọng hợp lý rằng một lớp có thể mở rộng một lớp theo cùng một cách mà một chuỗi nguyên mẫu có thể đến từ các nguyên mẫu khác không? Tại sao sự cần thiết cho các ceramony như vậy ẩn trong một plugin?
Sukima

Điều này đặc biệt gây khó chịu khi hầu hết các trường hợp sử dụng chỉ muốn tạo ra các đối tượng đơn giản chia sẻ hành vi. Một lỗi tùy chỉnh có thể sử dụng Error.toString(). Sự cần thiết phải thực hiện các vòng quay và chuyển hướng đặc biệt để thực hiện điều này có nghĩa là hầu hết các nhà phát triển sẽ tránh nó và dùng đến các thực hành xấu như ném dây thay vì Lỗi. Hoặc tạo bản đồ của riêng họ như các đối tượng. Tại sao cần phải ngăn chặn các phương pháp OOP như vậy?
Sukima

Theo tôi họ không chống lại nó, đó chỉ là một số vấn đề kỹ thuật. Tôi không chắc chắn mặc dù! Bạn có thể hỏi họ :) các dự án khá mở
Karel Bílek

Cho đến nay, tất cả các câu trả lời từ các câu hỏi mô phỏng về chủ đề này đều được để lại tại "babel không hỗ trợ điều đó" Tôi đoán rằng đó là kết thúc của cuộc trò chuyện. Thịt bò của tôi là thiếu sự hỗ trợ làm cho một thành ngữ OOP phổ biến trở nên khó khăn và tôi thậm chí đã phải chiến đấu với những người đồng nghiệp để có được chúng trong cuộc hành trình sôi nổi. Tôi chỉ muốn ở đây là một cách giải quyết thay thế sạch sẽ. Có vẻ như thêm một plugin là sự lựa chọn tốt nhất sau đó.
Sukima

15

Chỉnh sửa : Phá vỡ các thay đổi trong Bản in 2.1

Việc mở rộng các phần dựng sẵn như Lỗi, Mảng và Bản đồ có thể không còn hoạt động.

Theo khuyến nghị, bạn có thể tự điều chỉnh nguyên mẫu ngay lập tức sau bất kỳ cuộc gọi siêu (...) nào.

Chỉnh sửa câu trả lời ban đầu của Lee Benson một chút làm việc cho tôi. Điều này cũng thêm stackvà các phương thức bổ sung của ExtendableErrorlớp vào thể hiện.

class ExtendableError extends Error {
   constructor(message) {
       super(message);
       Object.setPrototypeOf(this, ExtendableError.prototype);
       this.name = this.constructor.name;
   }
   
   dump() {
       return { message: this.message, stack: this.stack }
   }
 }    

class MyError extends ExtendableError {
    constructor(message) {
        super(message);
        Object.setPrototypeOf(this, MyError.prototype);
    }
}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror.dump());
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

1
Bạn cũng cần gọi Object.setPrototypeOftrong hàm MyErrortạo. stackoverflow.com/a/41102306/186334 github.com/Microsoft/TypeScript-wiki/blob/master/ chủ
CallMeLaNN

10

Với những thay đổi mới nhất trong babel 6, tôi thấy biến đổi-dựng sẵn không còn hoạt động. Tôi đã kết thúc bằng cách sử dụng phương pháp hỗn hợp này:

export default class MyError {
    constructor (message) {
        this.name = this.constructor.name;
        this.message = message;
        this.stack = (new Error(message)).stack;
    }
}

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

import MyError from './MyError';

export default class MyChildError extends MyError {
    constructor (message) {
        super(message);
    }
}

Kết quả là tất cả các bài kiểm tra này đều vượt qua:

const sut = new MyError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut.name).toBe('MyError');
expect(typeof sut.stack).toBe('string');

const sut = new MyChildError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut).toBeInstanceOf(MyChildError);
expect(sut.name).toBe('MyChildError');
expect(typeof sut.stack).toBe('string');

6

Trích dẫn

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

Không cần this.stack = (new Error()).stack;lừa nhờ super()cuộc gọi.

Mặc dù các mã trên không thể xuất ra dấu vết ngăn xếp trừ khi this.stack = (new Error()).stack;hoặc Error.captureStackTrace(this, this.constructor.name);được gọi trong Babel . IMO, nó có thể là một vấn đề ở đây.

Trên thực tế, theo dõi ngăn xếp có thể được xuất ra dưới Chrome consoleNode.js v4.2.1với đoạn mã này.

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

var myerr = new MyError("test");
console.log(myerr.stack);
console.log(myerr);

Đầu ra của Chrome console.

MyError: test
    at MyError (<anonymous>:3:28)
    at <anonymous>:12:19
    at Object.InjectedScript._evaluateOn (<anonymous>:875:140)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)
    at Object.InjectedScript.evaluate (<anonymous>:664:21)

Đầu ra của Node.js

MyError: test
    at MyError (/home/bsadmin/test/test.js:5:8)
    at Object.<anonymous> (/home/bsadmin/test/test.js:11:13)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (module.js:442:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Function.Module.runMain (module.js:467:10)
    at startup (node.js:134:18)
    at node.js:961:3

4

Ngoài câu trả lời @zangw, bạn có thể xác định lỗi của mình như thế này:

'use strict';

class UserError extends Error {
  constructor(msg) {
    super(msg);
    this.name = this.constructor.name;
  }
}

// define errors
class MyError extends UserError {}
class MyOtherError extends UserError {}

console.log(new MyError instanceof Error); // true

throw new MyError('My message');

Nó sẽ ném đúng tên, tin nhắn và stacktrace:

MyError: My message
    at UserError (/Users/honzicek/Projects/api/temp.js:5:10)
    at MyError (/Users/honzicek/Projects/api/temp.js:10:1)
    at Object.<anonymous> (/Users/honzicek/Projects/api/temp.js:14:7)
    at Module._compile (module.js:434:26)
    at Object.Module._extensions..js (module.js:452:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:475:10)
    at startup (node.js:117:18)
    at node.js:951:3

4
Điều này không hoạt động : new MyError('foo') instanceof MyError === false.
Sukima

1
Nó làm, trên Node.js v7.7.3.
Gunar Gessner

2

Tôi đang cố gắng mở rộng Lỗi với ES6

class MyError extends Error {…}Cú pháp đó là chính xác.

Lưu ý rằng các bộ chuyển đổi vẫn có vấn đề với việc kế thừa từ các đối tượng dựng sẵn. Trong trường hợp của bạn,

var err = super(m);
Object.assign(this, err);

dường như để khắc phục vấn đề.


Thật! Nhưng dù sao thì thông điệp vẫn chưa được đặt - tôi sẽ viết một ví dụ mới.
Karel Bílek

Tôi đã viết lại ví dụ bây giờ
Karel Bílek


"Super (m)" sẽ trả về một đối tượng trống, rõ ràng. Vì vậy, Object.assign không giúp được gì.
Karel Bílek

@ KarelBílek: Bạn đang sử dụng trình duyệt nào? Error.call()không trả về một trường hợp lỗi mới cho tôi.
Bergi

2

Với câu trả lời này được chấp nhận không còn hoạt động, bạn luôn có thể sử dụng một nhà máy như một sự thay thế ( repl ):

function ErrorFactory(name) {
   return class AppError extends Error {
    constructor(message) {
      super(message);
      this.name = name;
      this.message = message; 
      if (typeof Error.captureStackTrace === 'function') {
        Error.captureStackTrace(this, this.constructor);
      } else { 
        this.stack = (new Error(message)).stack; 
      }
    }
  }     
}

// now I can extend
const MyError = ErrorFactory("MyError");


var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);


Câu trả lời được chấp nhận vẫn hoạt động với tôi, nếu bạn có các plugin babel cần thiết. Cảm ơn vì câu trả lời này, mặc dù!
Karel Bílek

2

Tôi thích cú pháp mạnh hơn mô tả ở trên. Các phương pháp bổ sung ở loại lỗi sẽ giúp bạn tạo đẹp console.loghoặc một cái gì đó khác.

export class CustomError extends Error {
    /**
     * @param {string} message
     * @param {number} [code = 0]
     */
    constructor(message, code = 0) {
        super();

        /**
         * @type {string}
         * @readonly
         */
        this.message = message;

        /**
         * @type {number}
         * @readonly
         */
        this.code = code;

        /**
         * @type {string}
         * @readonly
         */
        this.name = this.constructor.name;

        /**
         * @type {string}
         * @readonly
         */
        this.stack = CustomError.createStack(this);
    }

    /**
     * @return {string}
     */
    toString() {
        return this.getPrettyMessage();
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        return `${this.message} Code: ${this.code}.`;
    }

    /**
     * @param {CustomError} error
     * @return {string}
     * @private
     */
    static createStack(error) {
        return typeof Error.captureStackTrace === 'function'
            ? Error.captureStackTrace(error, error.constructor)
            : (new Error()).stack;
    }
}

Để kiểm tra mã này, bạn có thể chạy một cái gì đó tương tự:

try {
    throw new CustomError('Custom error was thrown!');
} catch (e) {
    const message = e.getPrettyMessage();

    console.warn(message);
}

Mở rộng các CustomErrorloại được chào đón. Có thể thêm một số chức năng cụ thể vào loại mở rộng hoặc ghi đè hiện có. Ví dụ.

export class RequestError extends CustomError {
    /**
     * @param {string} message
     * @param {string} requestUrl
     * @param {number} [code = 0]
     */
    constructor(message, requestUrl, code = 0) {
        super(message, code);

        /**
         * @type {string}
         * @readonly
         */
        this.requestUrl = requestUrl;
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        const base = super.getPrettyMessage();

        return `${base} Request URL: ${this.requestUrl}.`;
    }
}

1

Như @sukima đề cập, bạn không thể mở rộng JS gốc. Câu hỏi của OP không thể được trả lời.

Tương tự như câu trả lời của Melbourne2991 , tôi đã sử dụng một nhà máy hơn, nhưng tuân theo khuyến nghị của MDN cho các loại lỗi của khách hàng .

function extendError(className){
  function CustomError(message){
    this.name = className;
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
  CustomError.prototype = Object.create(Error.prototype);
  CustomError.prototype.constructor = CustomError;
  return CustomError;
}

1

Điều này làm việc cho tôi:

/**
 * @class AuthorizationError
 * @extends {Error}
 */
export class AuthorizationError extends Error {
    message = 'UNAUTHORIZED';
    name = 'AuthorizationError';
}

0

Không sử dụng Babel, nhưng trong ES6 đơn giản, những điều sau đây có vẻ hoạt động tốt với tôi:

class CustomError extends Error {
    constructor(...args) {
        super(...args);
        this.name = this.constructor.name;
    }
}

Kiểm tra từ REPL:

> const ce = new CustomError('foobar');
> ce.name
'CustomError'
> ce.message
'foobar'
> ce instanceof CustomError
true
> ce.stack
'CustomError: foobar\n    at CustomError (repl:3:1)\n ...'

Như bạn có thể thấy, ngăn xếp chứa cả tên lỗi và thông báo. Tôi không chắc là mình có thiếu thứ gì không, nhưng tất cả các câu trả lời khác dường như quá phức tạp.


0

Tôi đã cải thiện một chút giải pháp của @Lee Benson theo cách này:

extableError.js

class ExtendableError extends Error {
    constructor(message, errorCode) {
        super(message);
        this.name = this.constructor.name;
        this.errorCode = errorCode
        if (typeof Error.captureStackTrace === 'function') {
            Error.captureStackTrace(this, this.constructor);
        } else {
            this.stack = (new Error(message)).stack;
        }
    }


}

export default ExtendableError

một ví dụ về lỗi

import ExtendableError from './ExtendableError'

const AuthorizationErrors = {
    NOT_AUTHORIZED: 401,
    BAD_PROFILE_TYPE: 402,
    ROLE_NOT_ATTRIBUTED: 403
}

class AuthorizationError extends ExtendableError {
    static errors = AuthorizationErrors 
}

export default AuthorizationError 

Sau đó, bạn có thể nhóm các lỗi trong khi có các chỉ định tùy chọn để quyết định những việc cần làm khác nhau trong một số tình huống cụ thể của ứng dụng của bạn

new AuthorizationError ("The user must be a seller to be able to do a discount", AuthorizationError.errors.BAD_PROFILE_TYPE )
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.