it-roy-ru.com

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

Я пытаюсь расширить Ошибка с 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 никогда не получает правильный набор сообщений.

Попробуйте в Вавилоне REPL .

Теперь я видел несколько решений по SO ( например, здесь ), но все они кажутся совершенно не-ES6-у. Как сделать это в стиле Nice, ES6? (Это работает в Вавилоне)

120
Karel Bílek

Исходя из ответа Карела Билека, я бы внес небольшое изменение в 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 ), чтобы это работало.

166
Lee Benson

Объединив этот ответ , этот ответ и этот код , я сделал этот небольшой класс "помощника", который, кажется, работает нормально.

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

38
Karel Bílek

Чтобы наконец положить это на отдых. В Вавилоне 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

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

24
Sukima

Edit: прерывание изменений в TypeScript 2.1

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

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

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

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

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');
8
Diego Ferri

Цитата

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

Нет необходимости в трюке this.stack = (new Error()).stack; благодаря вызову super().

Хотя вышеприведенные коды не могут вывести трассировку стека, если в Babel не вызывается this.stack = (new Error()).stack; или Error.captureStackTrace(this, this.constructor.name);. ИМО, это может быть одна проблема здесь.

На самом деле, трассировка стека может быть выведена под 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

В дополнение к ответу @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
Honza Stepanovsky

Я пытаюсь расширить Ошибка с ES6

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

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

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

кажется, чтобы решить проблему.

2
Bergi

Учитывая, что принятый ответ больше не работает, вы всегда можете использовать фабрику в качестве альтернативы ( 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);

2
Melbourne2991

Это работает для меня:

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

Как упоминает @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
Eric H.

Я предпочитаю более сильный синтаксис, чем описано выше. Дополнительные методы в типе ошибки помогут вам создать красивый 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
B. Bohdan

Не используя 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
JHH