Розширення помилки у Javascript із синтаксисом ES6 та Babel


132

Я намагаюся поширити помилку за допомогою ES6 та Babel. Це не виходить.

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

Об'єкт Error ніколи не отримує правильний набір повідомлень.

Спробуйте в Babel REPL .

Зараз я бачив декілька рішень щодо SO ( наприклад, тут ), але всі вони здаються дуже не-ES6-y. Як зробити це в приємний спосіб ES6? (Це працює в Бабелі)


2
Після вашого посилання на Babel REPL, схоже, вказує на те, що він працює зараз правильно. Я припускаю, що помилка в Вавілоні була з тих пір виправлена.
kybernetikos

Відповіді:


188

Виходячи з відповіді Карела Білека, я змінив би 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);

Це буде друком MyErrorу стеці, а не загальному Error.

Він також додасть повідомлення про помилку до сліду стека - якого не було в прикладі Карела.

Він також буде використовуватись, captureStackTraceякщо є.

З Babel 6 для цього вам потрібна вбудована трансформація ( npm ).


1
@MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . Я заперечую, що краще використовувати цю функцію, якщо вона доступна, оскільки вона забезпечує "рідніший" стек виклику та друкує ім'я об'єкта помилки. Звичайно, якщо ви використовуєте це виключно на стороні сервера (Node), то це також не є проблемою.
Лі Бенсон

4
@MichaelYounkin Я не думаю, що це заслуговує на спад. ОП говорила про поширення помилок в ES6. Дотримуючись такої логіки, майже весь ES6 відсутній принаймні в одному браузері. Моє рішення (із доданою функцією перевірки функцій) забезпечує нативне покриття у найбільш широко використовуваному веб-переглядачі, резервне копіювання у всіх інших та 100% покриття в Node.js. Я погоджуюся, що якщо ви, яка помилка classname послідовно, то this.stack = (new Error(message)).stackотримуєте це, але ... на практиці, це, мабуть, не величезна справа.
Лі Бенсон

6
Це не працює в Вавилоні 6:new MyError('foo') instanceof MyError === false
Сукіма

5
Цей код попередньо складено з babel як модуль NPM: extendable-error-class npmjs.com/package/extendable-error-class, що зручно уникати залежності від babel-plugin-transform-
builtin-extension

3
this.message = message;зайвийsuper(message);
матійг

39

Поєднуючи цю відповідь , цю відповідь і цей код , я зробив цей невеликий клас "помічник", який, здається, працює добре.

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

Спробуйте в REPL


1
this.stack = (new Error(message)).stack;- інакше повідомлення відсутнє в стек-трек
Lee Benson

3
Я підозрюю, що це не працює в міру необхідності, тому що якщо ви це зробите: console.log (myerror instanceof ExtendableError); воно все ще говорить помилково ..
Mauno Vähä

4
та ж проблема, що використання instanceof CustomError не працює, що сенс розширюється, якщо ви не можете використовувати instanceof.
gre

Це можна покращити, додавши messageв конструктор стека помилок, тож він показує правильне повідомлення у верхній частині стека при відкиданні:this.stack = (new Error(message)).stack;
Sebastien

1
myerror.nameтепер повертає "Помилка". Не впевнений, що це пов’язано з пізнішими версіями babel.See @ відповідь sukima нижче
Ерік Х.

27

Щоб остаточно покласти це спокою. У Вавилоні 6 це явно , що розробники не підтримують розширення від вбудованого. Хоча цей трюк НЕ допоможе з речами , як Map, Setі т.д. , це робить роботу Error. Це важливо, оскільки одна з основних ідей мови, яка може спричинити виняток, - це дозволити користувацькі помилки. Це вдвічі важливіше, оскільки обіцянки стають більш корисними, оскільки вони призначені для відхилення помилки .

Сумна правда полягає в тому, що вам потрібно все-таки виконати це по-старому в ES2015.

Приклад у Вавілонській REPL

Спеціальна модель помилок

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

З іншого боку, є плагін для Babel 6, який дозволяє це зробити.

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

Оновлення: (станом на 2016-09-29) Після деяких тестувань виявляється, що babel.io не відповідає належним чином усім твердженням (що походить від користувацької розширеної помилки). Але в Ember.JS розширення помилки працює як очікується: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce


так, з приєднаним плагіном Babel він працює правильно з прийнятою відповіддю. (Однак, отриманий файл не працює в Node, оскільки він, мабуть, не відображає)
Karel Bílek

Як цікавість, якщо специфікації ES2016 говорять про те, що вбудовані модулі можна розширити, чому вмс, як v8, так і Babel's es5, перетворюються так проти цього? Чи не є розумним сподіванням, що клас може розширити клас так само, як ланцюг прототипу може походити з інших прототипів? Чому необхідність такої керамонії ховається у плагіні?
Сукіма

Це особливо страшно, коли більшість випадків використання просто хочуть зробити прості об'єкти, які поділяють поведінку. Спеціальна помилка, яку можна використовувати Error.toString(). Необхідність робити спеціальні обручі та гірації для цього означає, що більшість дияволів уникнуть цього і вдасться до поганих практик, таких як метання струн замість помилок. Або зробити власну Карту як об’єкти. Чому потрібно стримувати такі методи ООП?
Сукіма

На мою думку, вони не проти, це лише якесь технічне питання. Я не впевнений, хоча! Ви можете запитати їх :) проекти досить відкриті
Карел Білек

Поки всі відповіді на симуляційні запитання по темі залишаються на "бабін не підтримує, що" я зрозумів, що це було закінченням розмови. Моя яловичина полягає в тому, що відсутність підтримки утруднює загальну ідіому OOP, і мені навіть довелося боротись з кооператорами, щоб перенести їх на суглоб котла. Я просто бажаю, щоб тут було чисте альтернативне рішення. Здається, додавання плагіна - це найкращий вибір.
Сукіма

15

Редагування : порушення змін у Typescript 2.1

Розширення таких вбудованих програм, як помилка, масив та карта, може більше не працювати.

Як рекомендація, ви можете вручну налаштувати прототип відразу після будь-яких супер (...) дзвінків.

Редагування оригінальної відповіді Лі Бенсона трохи працює для мене. Це також додає stackі додаткові методи ExtendableErrorкласу до екземпляра.

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
Вам також потрібно зателефонувати Object.setPrototypeOfв MyErrorконструктор. stackoverflow.com/a/41102306/186334 github.com/Microsoft/TypeScript-wiki/blob/master / ...
CallMeLaNN

10

З останніми змінами у вавіллі 6 я вважаю, що вбудований трансформатор-розширення вже не працює. Я закінчив, використовуючи цей змішаний підхід:

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

В результаті всі ці тести проходять:

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

Цитуючи

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

Немає потреби в this.stack = (new Error()).stack;фокусі завдяки super()дзвінку.

Хоча вищенаведені коди не можуть вивести слід стека, якщо this.stack = (new Error()).stack;або Error.captureStackTrace(this, this.constructor.name);не викликається в Babel . ІМО, це, можливо, одне питання тут.

Насправді слід стека може бути виведений під фрагментами цього коду Chrome consoleта за Node.js v4.2.1допомогою цього фрагмента.

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

Вихід 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)

Вихід 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

Окрім @zangw відповіді, ви можете визначити свої помилки так:

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

яка викидає правильне ім'я, повідомлення та стеження:

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
Це не працює: new MyError('foo') instanceof MyError === false.
Сукіма

1
Це робить, на Node.js v7.7.3.
Гунар Гесснер

2

Я намагаюся поширити помилку з ES6

Цей class MyError extends Error {…}синтаксис правильний.

Зауважте, що у транспіляторів все ще виникають проблеми із успадкуванням від вбудованих об'єктів. У вашому випадку,

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

здається, вирішує проблему.


Правда! Але повідомлення все одно не встановлено - я напишу новий приклад.
Карел Білек

Я переписав приклад зараз
Карел Білек


"Супер (м)", мабуть, поверне порожній об'єкт. Тож Object.assign не допомагає.
Карел Білек

@ KarelBílek: Який браузер ви використовуєте? Error.call()повертає для мене новий екземпляр помилки.
Бергі

2

Враховуючи це, прийнята відповідь більше не працює, ви завжди можете використовувати завод як альтернативу ( 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);


Прийнята відповідь все ще працює для мене, якщо у вас є необхідні плагіни Babel. Дякую також за цю відповідь!
Карел Білек

2

Я віддаю перевагу сильнішому синтаксису, ніж описано вище. Додаткові методи типу помилки допоможуть вам створити гарненьке console.logчи щось інше.

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

Щоб перевірити цей код, ви можете запустити щось подібне:

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

    console.warn(message);
}

Розширення CustomErrorтипу вітається. Можна додати певний функціонал до розширеного типу або змінити існуючий. Наприклад.

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

Як зазначає @sukima, ви не можете поширити рідний JS. На питання ОП не можна відповісти.

Подібно до відповіді Melbourne2991 , я скоріше використовував фабрику, але дотримувався рекомендацій MDN щодо типів помилок клієнта .

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

Це працює для мене:

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

0

Не використовуючи Babel, але в звичайному ES6, мені здається, що це працює нормально:

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

Тестування з 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 ...'

Як бачите, стек містить і ім'я помилки, і повідомлення. Я не впевнений, чи щось мені не вистачає, але всі інші відповіді здаються надто складними.


0

Я трохи вдосконалив рішення @Lee Benson таким чином:

extendableError.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

приклад помилки

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 

Тоді ви можете групувати помилки, маючи специфікатори опцій, щоб вирішити, що робити інакше в деяких конкретних ситуаціях із вашим додатком

new AuthorizationError ("The user must be a seller to be able to do a discount", AuthorizationError.errors.BAD_PROFILE_TYPE )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.