Расширение ошибки в Javascript с синтаксисом ES6 и Babel


132

Я пытаюсь расширить Error с помощью 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, похоже, указывает на то, что теперь он работает правильно. Я предполагаю, что это была ошибка в Babel, которая с тех пор исправлена.
— 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 вам понадобится transform-builtin-extend ( npm ), чтобы это работало.


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

4
@MichaelYounkin Я не думаю, что это заслуживает отрицательного голоса. OP говорил о расширении ошибок в ES6. Следуя этой логике, почти весь ES6 отсутствует как минимум в одном браузере. Мое решение (с добавленной проверкой функций) обеспечивает собственное покрытие в наиболее широко используемом браузере, откат во всех остальных и 100% покрытие в Node.js. Я согласен с тем, что если вы последовательно this.stack = (new Error(message)).stackвыбираете имя класса ошибки, то получаете это ... но на практике это, вероятно, не имеет большого значения.
— Ли Бенсон

6
Это не работает в Babel 6:new MyError('foo') instanceof MyError === false
— Sukima

5
Этот код предварительно скомпилирован с помощью babel в качестве модуля NPM: extendable-error-class npmjs.com/package/extendable-error-class, что удобно, чтобы избежать зависимости от babel-plugin-transform-builtin-extend
— brillout

3
this.message = message;является избыточным сsuper(message);
— mathieug

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;- в противном случае сообщение отсутствует в stacktrace
— Ли Бенсон

3
Я подозреваю, что это не работает должным образом, потому что если вы это сделаете: console.log (myerror instanceof ExtendableError); он все еще говорит ложь ..
— Мауно Вяха

4
та же проблема, использование instanceof CustomError не работает, какой смысл расширять, если вы не можете использовать instanceof.
— gre

Его можно улучшить, добавив messageв конструктор стека ошибок, чтобы он отображал правильное сообщение вверху стека при вызове:this.stack = (new Error(message)).stack;
— Себастьян,

1
myerror.nameтеперь возвращает "Ошибка". Не уверен, связано ли это с более поздними версиями babel. См. Ответ @sukima ниже
— Эрик Х.

27

Чтобы наконец положить этому конец. В Babel 6 явно указано, что разработчики не поддерживают расширение из встроенных. Хотя этот трюк не поможет с такими вещами, как Map, Setи т.д., он работает Error. Это важно, поскольку одна из основных идей языка, который может генерировать исключение, - разрешить настраиваемые ошибки. Это вдвойне важно, поскольку промисы становятся более полезными, поскольку они предназначены для отклонения ошибок .

Печальная правда в том, что в ES2015 вам все еще нужно делать это по-старому.

Пример в Babel 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

Обновление: (по состоянию на 29.09.2016). После некоторого тестирования выяснилось, что babel.io не учитывает должным образом все утверждения (за счет расширенной пользовательской ошибки). Но в Ember.JS ошибка расширения работает должным образом: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce


да, со связанным плагином babel он работает правильно с принятым ответом. (Однако полученный файл не работает в Node, потому что, по-видимому, в нем нет Reflect)
— Карел Билек

Просто любопытно, если в спецификациях ES2016 сказано, что встроенные функции можно расширять, почему vms, такие как v8 и Babel es5, транслируются так против этого? Разве не разумно ожидать, что класс может расширять класс так же, как цепочка прототипов может исходить из других прототипов? Почему необходимость в такой церемонии скрыта в плагине?
— Sukima

Это особенно расстраивает, когда в большинстве случаев использования просто нужно создавать простые объекты с общим поведением. Пользовательская ошибка, которую можно использовать Error.toString(). Необходимость делать специальные обручи и повороты для достижения этой цели означает, что большинство разработчиков будут избегать этого и прибегать к плохим методам, таким как бросание струн вместо ошибок. Или сделать свою собственную карту как объекты. Почему нужно сдерживать такие методы ООП?
— Sukima

На мой взгляд, они не против, это чисто технический вопрос. Я не уверен, хотя! Вы можете спросить их :) проекты достаточно открытые
— Карел Билек

Пока все ответы на аналогичные вопросы по теме оставлены в виде «вавилон не поддерживает это». Я решил, что это конец разговора. Моя претензия заключается в том, что отсутствие поддержки делает распространенную идиому ООП сложной, и мне даже приходилось сражаться с соавторами, чтобы преодолеть шаблонный мусор. Я просто хочу, чтобы здесь был чистый альтернативный обходной путь. Кажется, тогда лучший выбор - добавить плагин.
— Sukima

15

Изменить : критические изменения в Typescript 2.1

Расширение встроенных модулей, таких как Error, Array и Map, может больше не работать.

В качестве рекомендации вы можете вручную настроить прототип сразу после любых вызовов super (...).

Редактирование оригинального ответа Ли Бенсона немного работает для меня. Это также добавляет к экземпляру 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 02

10

С последними изменениями в babel 6 я обнаружил, что transform-builtin-extend больше не работает. В итоге я использовал этот смешанный подход:

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.
— Sukima

1
Так оно и есть Node.js v7.7.3.
— Gessner

2

Я пытаюсь расширить Error с помощью ES6

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

Обратите внимание, что у транспиляторов все еще есть проблемы с наследованием от встроенных объектов. В твоем случае,

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

вроде решает проблему.


Правда! Но сообщение все равно не ставится - напишу новый пример.
— Karel Bílek

Я переписал пример сейчас
— Карел Билек


"Super (m)", по-видимому, вернет пустой объект. Так что Object.assign не помогает.
— Karel Bílek

@ KarelBílek: Какой браузер вы используете? Error.call()возвращает мне новый экземпляр ошибки.
— Bergi

2

Учитывая это, принятый ответ больше не работает, вы всегда можете использовать фабрику в качестве альтернативы ( ответ ):

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.