Расширение ошибки в 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

Sukima
источник
да, со связанным плагином 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
zangw
источник
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);

Melbourne2991
источник
Принятый ответ все еще работает для меня, если у вас есть необходимые плагины 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 ...'

Как видите, стек содержит как имя ошибки, так и сообщение. Я не уверен, что мне что-то не хватает, но все остальные ответы, кажется, слишком усложняют ситуацию.

JHH
источник
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 )
Зиед Хамди
источник