在JavaScript中扩展Error的好方法是什么?


Answers:


217

message属性唯一的标准字段Error对象是。(请参阅MDN或EcmaScript语言规范,第15.11节)其他所有内容都是特定于平台的。

Mosts环境设置stack属性,但fileNamelineNumber实际上是无用的继承使用。

因此,简约的方法是:

function MyError(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = (new Error()).stack;
}
MyError.prototype = new Error;  // <-- remove this if you do not 
                                //     want MyError to be instanceof Error

您可以嗅探堆栈,从堆栈中移走不需要的元素,并提取诸如fileName和lineNumber之类的信息,但是这样做需要有关当前正在运行JavaScript的平台的信息。大多数情况下都是不必要的-如果您确实需要,可以在事后检查中进行。

Safari是一个明显的例外。没有stack属性,但是有throw关键字集sourceURLline要抛出的对象的属性。这些事情肯定是正确的。

我使用的测试用例可以在这里找到:JavaScript自制的Error对象比较


19
您可以将this.name = 'MyError' 函数的外部移动并将其更改为MyError.prototype.name = 'MyError'
Daniel Beardsley

36
这是唯一正确的答案,尽管就样式而言,我可能会这样写。function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos 2012年

11
我也会补充MyError.prototype.constructor = MyError
巴拉特·哈特里

3
在ES6中Error.call(this,message); 应该初始化this吧?
4esn0k 2014年

4
MyError.prototype = Object.create(Error.prototype);
罗宾像那只鸟

170

在ES6中:

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

资源


53
值得一提的是,如果您通过转译器(例如Babel)使用ES6功能,那么这将不起作用,因为子类必须扩展一个类。
aaaidan

6
如果您正在使用babel并且在节点> 5.x上,则不应该使用es2015预设,但是npmjs.com/package/babel-preset-node5将允许您使用本机es6扩展及更多
Ace

2
如果可能的话,这是最好的方法。自定义错误的行为更像是Chrome和Firefox(可能还有其他浏览器)中的常规错误。
马特·布朗

2
对于浏览器,请注意,您可以在运行时检测到对类的支持,并相应地回退到非类版本。检测代码:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
马特·布朗

31
为了便于维护,请this.name = this.constructor.name;改用。
КонстантинВан

51

简而言之:

  • 如果您使用的是不带转译器的ES6 :

    class CustomError extends Error { /* ... */}
  • 如果您使用的是Babel Transpiler

选项1:使用babel-plugin-transform-b​​uiltin-extend

选项2:自己动手(从同一个库中汲取灵感)

    function CustomError(...args) {
      const instance = Reflect.construct(Error, args);
      Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
      return instance;
    }
    CustomError.prototype = Object.create(Error.prototype, {
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    Reflect.setPrototypeOf(CustomError, Error);
  • 如果您使用的是纯ES5

    function CustomError(message, fileName, lineNumber) {
      var instance = new Error(message, fileName, lineNumber);
      Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
      return instance;
    }
    CustomError.prototype = Object.create(Error.prototype, {
      constructor: {
        value: Error,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    if (Object.setPrototypeOf){
        Object.setPrototypeOf(CustomError, Error);
    } else {
        CustomError.__proto__ = Error;
    }
  • 替代方案:使用Classtrophobic框架

说明:

为什么使用ES6和Babel扩展Error类是一个问题?

因为CustomError实例不再被这样识别。

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

事实上,从巴贝尔的官方文档,你不能扩展任何内置的JavaScript类DateArrayDOMError

问题描述如下:

那么其他答案呢?

所有给出的答案都解决了该instanceof问题,但是您丢失了常规错误console.log

console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵    at CustomError (<anonymous>:4:19)↵    at <anonymous>:1:5"}

而使用上述方法,不仅可以解决instanceof问题,还可以保留常规错误console.log

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5

1
class CustomError extends Error { /* ... */}无法正确处理供应商特定的参数(lineNumber,等),“使用ES6语法扩展Javascript中的错误”是Babel特定的,您的ES5解决方案使用了const它,并且不处理自定义参数。
Indolering

非常完整的答案。
Daniele Orlando,

实际上,这提供了最全面的解决方案,并解释了为什么需要各种部件。非常感谢JBE!
AndrewSouthpaw

这帮助我解决了从“错误”继承的问题。这是两个小时的噩梦!
Iulian Pinzaru '19

2
值得一提的是,console.log(new CustomError('test') instanceof CustomError);// false撰写本文时存在问题,但现已解决。实际上,答案中链接的问题已解决,我们可以在此处测试正确的行为并通过将代码粘贴到REPL中,并查看如何正确地将其转译以使用正确的原型链实例化。
大雄

44

编辑:请阅读评论。事实证明,这仅在V8(Chrome / Node.JS)中有效。我的意图是提供一个跨浏览器解决方案,该解决方案可在所有浏览器中使用,并在支持的地方提供堆栈跟踪。

编辑:我做了这个社区Wiki,以便进行更多编辑。

V8(Chrome / Node.JS)的解决方案可在Firefox中运行,并且可以修改为在IE中正常运行。(请参阅发布结束)

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
  Error.call(this) // Does not seem necessary. Perhaps remove this line?
  Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
  this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
  this.message = message; // Used to set the message
}

原始帖子“显示代码!”

简洁版本:

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype
  Error.captureStackTrace(this, this.constructor)
  this.name = this.constructor.name
  this.message = message
}

我保留this.constructor.prototype.__proto__ = Error.prototype在函数内部,以将所有代码保持在一起。但是,您也可以this.constructor使用UserError和进行替换,它允许您将代码移至函数外部,因此仅被调用一次。

如果您沿那条路线行驶,请确保在第一次掷球之前致电该线路UserError

注意事项不适用该功能,因为无论顺序如何,都首先创建功能。因此,您可以毫无问题地将函数移至文件末尾。

浏览器兼容性

可在Firefox和Chrome(和Node.JS)中使用,并兑现了所有承诺。

Internet Explorer在以下方面失败

  • 错误不必从头err.stack开始,因此“这不是我的错”。

  • Error.captureStackTrace(this, this.constructor) 不存在,所以您需要做其他类似的事情

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
  • toString当你继承子类时,不再存在Error。因此,您还需要添加。

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE不会考虑UserError到是instanceof Error,除非你运行下面你前一段时间throw UserError

    UserError.prototype = Error.prototype

16
我不认为Firefox实际上具有captureStackTrace。这是V8扩展程序,在Firefox中对我来说是未定义的,在网络上也找不到支持它的Firefox引用。(不过,谢谢!)
Geoff

5
Error.call(this)确实没有做任何事情,因为它返回错误而不是修改this
kybernetikos 2012年

1
非常适合Node.js
Rudolf Meijering

1
UserError.prototype = Error.prototype有误导性。这不做继承,这使它们成为同一个类
Halcyon

1
至少对于当前的浏览器来说,我认为它Object.setPrototypeOf(this.constructor.prototype, Error.prototype)是首选this.constructor.prototype.__proto__ = Error.prototype
ChrisV

29

为了避免出现每种不同类型的错误,我将一些解决方案的精髓结合到一个  createErrorType函数中:

function createErrorType(name, init) {
  function E(message) {
    if (!Error.captureStackTrace)
      this.stack = (new Error()).stack;
    else
      Error.captureStackTrace(this, this.constructor);
    this.message = message;
    init && init.apply(this, arguments);
  }
  E.prototype = new Error();
  E.prototype.name = name;
  E.prototype.constructor = E;
  return E;
}

然后,您可以轻松定义新的错误类型,如下所示:

var NameError = createErrorType('NameError', function (name, invalidChar) {
  this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});

var UnboundError = createErrorType('UnboundError', function (variableName) {
  this.message = 'Variable ' + variableName + ' is not bound';
});

您仍然有需要排队的理由this.name = name;吗?
彼得·曾

@PeterTseng因为name已经在原型上设置了,所以不再需要了。我删除了 谢谢!
Ruben Verborgh '16

27

2018年,我认为这是最好的方法; 支持IE9 +和现代浏览器。

更新:请参阅此测试和存储库,以比较不同的实现。

function CustomError(message) {
    Object.defineProperty(this, 'name', {
        enumerable: false,
        writable: false,
        value: 'CustomError'
    });

    Object.defineProperty(this, 'message', {
        enumerable: false,
        writable: true,
        value: message
    });

    if (Error.hasOwnProperty('captureStackTrace')) { // V8
        Error.captureStackTrace(this, CustomError);
    } else {
        Object.defineProperty(this, 'stack', {
            enumerable: false,
            writable: false,
            value: (new Error(message)).stack
        });
    }
}

if (typeof Object.setPrototypeOf === 'function') {
    Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
    CustomError.prototype = Object.create(Error.prototype, {
        constructor: { value: CustomError }
    });
}

还请注意,不赞成使用__proto__属性,该属性在其他答案中广泛使用。


1
你为什么用setPrototypeOf()?至少根据MDN而言,通常不建议您使用它,只要您可以通过.prototype在构造函数上设置属性来完成同一件事(就像您在else没有的浏览器中所做的那样setPrototypeOf)。
马特·布朗

不建议一起更改对象的原型,而不是setPrototypeOf。但是,如果仍然需要它(按照OP的要求),则应使用内置方法。正如MDN所指出的那样,这被认为是设置对象原型的正确方法。换句话说,MDN表示不要更改原型(因为它会影响性能和优化),但如果需要,请使用setPrototypeOf
OnurYıldırım16年

我的观点是,我认为您实际上不需要在此处更改原型。您只需在底部(CustomError.prototype = Object.create(Error.prototype))处使用一行即可。此外,Object.setPrototypeOf(CustomError, Error.prototype)是设置构造函数本身的原型,而不是为的新实例指定原型CustomError。无论如何,在2016年我觉得其实有更好的方法来扩展的错误,但我仍然搞清楚如何与巴贝尔一起使用它:github.com/loganfsmyth/babel-plugin-transform-b​​uiltin-extend/...
马特·布朗

CustomError.prototype = Object.create(Error.prototype)也正在改变原型。由于ES5中没有内置的扩展/继承逻辑,因此您必须对其进行更改。我敢肯定,您提到的babel插件会做类似的事情。
OnurYıldırım16年

1
我创建了一个要点,以说明为什么Object.setPrototypeOf在这里使用无意义,至少在您使用时没有用:gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348。也许您打算写Object.setPrototypeOf(CustomError.prototype, Error.prototype)-这会更有意义(尽管与简单设置相比仍然没有任何好处CustomError.prototype)。
马特·布朗

19

出于完整性的考虑(仅因为以前的答案中没有提到此方法),如果您使用的是Node.js,而不必关心浏览器的兼容性,则内置的功能很容易实现所需的效果inheritsutil模块(此处为官方文档)。

例如,假设您要创建一个自定义错误类,该类将错误代码作为第一个参数,将错误消息作为第二个参数:

文件custom-error.js

'use strict';

var util = require('util');

function CustomError(code, message) {
  Error.captureStackTrace(this, CustomError);
  this.name = CustomError.name;
  this.code = code;
  this.message = message;
}

util.inherits(CustomError, Error);

module.exports = CustomError;

现在您可以实例化和传递/抛出您的CustomError

var CustomError = require('./path/to/custom-error');

// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));

// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');

请注意,使用此代码段,堆栈跟踪将具有正确的文件名和行,并且错误实例将具有正确的名称!

发生这种情况是由于captureStackTrace方法的使用,该方法stack在目标对象上创建了一个属性(在这种情况下,CustomError实例化了该属性)。有关其工作原理的更多详细信息,请参见此处的文档。


1
this.message = this.message;这是错误的还是关于JS我还不了解的疯狂事情?
亚历克斯(Alex)

1
嘿@Alex,您完全正确!现在已修复。谢谢!
维克多·施罗德

18

Crescent Fresh的答案极富争议,令人误解。尽管他的警告无效,但他没有解决其他限制。

首先,Crescent的“注意事项”(Caveats :)段落中的推理没有任何意义。这种解释意味着,与多个catch语句相比,编码“一堆if(错误(MyError的MyError实例)else ...”)在某种程度上是繁重的或冗长的。单个catch块中的多个instanceof语句与多个catch语句一样简洁-简洁简洁的代码,没有任何技巧。这是模拟Java出色的特定于可抛出子类型的错误处理的好方法。

WRT“似乎未设置子类的message属性”,如果您使用正确构造的Error子类,则情况并非如此。要创建自己的ErrorX Error子类,只需复制以“ var MyError =”开头的代码块,将一个单词“ MyError”更改为“ ErrorX”。(如果要向子类添加自定义方法,请遵循示例文本)。

JavaScript错误子类的真正且显着的局限性是,对于跟踪并报告堆栈跟踪和实例化位置的JavaScript实现或调试器(如FireFox),您自己的Error子类实现中的位置将被记录为该实例的实例化点。类,而如果您使用直接错误,它将是您运行“新错误(...)”的位置。IE用户可能永远不会注意到,但是FF上的Fire Bug用户将在这些错误旁边看到无用的文件名和行号值报告,并且必须深入到元素1的堆栈跟踪中才能找到真正的实例化位置。


我是否理解正确-如果您不继承并直接使用new Error(...),那么文件名和行将被正确报告?而且您基本上说,在实践中(真正的而不仅仅是性感或装饰性的)将错误归为错误,没有意义吗?
jayarjo 2011年

6
这个答案有些令人困惑,因为Crescent Fresh's已被删除!
彼得·


13

这个解决方案怎么样?

而不是使用以下方法抛出自定义错误:

throw new MyError("Oops!");

您将包装Error对象(有点像Decorator):

throw new MyError(Error("Oops!"));

这样可以确保所有属性都是正确的,例如堆栈,fileName lineNumber等。

然后,您要做的就是复制属性或为它们定义getter。这是使用吸气剂(IE9)的示例:

function MyError(wrapped)
{
        this.wrapped = wrapped;
        this.wrapped.name = 'MyError';
}

function wrap(attr)
{
        Object.defineProperty(MyError.prototype, attr, {
                get: function()
                {
                        return this.wrapped[attr];
                }
        });
}

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

wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');

MyError.prototype.toString = function()
{
        return this.wrapped.toString();
};

1
我已经以npm软件包的形式发布了该解决方案:npmjs.com/package/throwable
JoWie 2015年

非常出色的解决方案,感谢您的分享!一种变化:new MyErr (arg1, arg2, new Error())在MyErr构造函数中,我们用于Object.assign将最后一个arg的属性分配给this
Peeyush Kushwaha

我喜欢这个。通过使用封装而不是继承来绕过限制。
珍妮·奥莱利

13

正如某些人所说,使用ES6相当容易:

class CustomError extends Error { }

所以我在我的应用程序(Angular,Typescript)中尝试了一下,但是它没有用。一段时间后,我发现问题来自Typescript:O

参见https://github.com/Microsoft/TypeScript/issues/13965

这很令人不安,因为如果您这样做:

class CustomError extends Error {}


try {
  throw new CustomError()
} catch(e) {
  if (e instanceof CustomError) {
    console.log('Custom error');
  } else {
    console.log('Basic error');
  }
}

在节点中或直接在浏览器中,它将显示: Custom error

尝试在Typescript操场上的项目中使用Typescript运行它,它将显示Basic error...

解决方案是执行以下操作:

class CustomError extends Error {
  // we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
  // otherwise we cannot use instanceof later to catch a given type
  public __proto__: Error;

  constructor(message?: string) {
    const trueProto = new.target.prototype;
    super(message);

    this.__proto__ = trueProto;
  }
}

10

我的解决方案比提供的其他答案更简单,并且没有缺点。

它保留了Error原型链和Error的所有属性,而无需对其有特定的了解。它已经在Chrome,Firefox,Node和IE11中进行了测试。

唯一的限制是在调用堆栈顶部的一个额外条目。但这很容易被忽略。

这是带有两个自定义参数的示例:

function CustomError(message, param1, param2) {
    var err = new Error(message);
    Object.setPrototypeOf(err, CustomError.prototype);

    err.param1 = param1;
    err.param2 = param2;

    return err;
}

CustomError.prototype = Object.create(
    Error.prototype,
    {name: {value: 'CustomError', enumerable: false}}
);

用法示例:

try {
    throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
    console.log(ex.name); //CustomError
    console.log(ex.message); //Something Unexpected Happened!
    console.log(ex.param1); //1234
    console.log(ex.param2); //neat
    console.log(ex.stack); //stacktrace
    console.log(ex instanceof Error); //true
    console.log(ex instanceof CustomError); //true
}

对于需要setPrototypeOf的polyfil的环境:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};

1
如我的回答中所述,此解决方案可能会在Firefox或其他仅在控制台中记录堆栈跟踪的第一行的浏览器中引起问题
Jonathan Benn

这是我发现的唯一可以与ES5配合使用的答案(使用ES6类也可以配合使用)。与其他答案相比,Chromium DevTools中的错误显示更好。
Ben Gubler

注意:如果您将此解决方案与TypeScript一起使用,则必须运行,throw CustomError('err')而不是throw new CustomError('err')
Ben Gubler

8

在上面的示例中Error.apply(也Error.call)对我没有任何帮助(Firefox 3.6 / Chrome 5)。我使用的解决方法是:

function MyError(message, fileName, lineNumber) {
    var err = new Error();

    if (err.stack) {
        // remove one stack level:
        if (typeof(Components) != 'undefined') {
            // Mozilla:
            this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
        }
        else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
            // Google Chrome/Node.js:
            this.stack = err.stack.replace(/\n[^\n]*/,'');
        }
        else {
            this.stack = err.stack;
        }
    }
    this.message    = message    === undefined ? err.message    : message;
    this.fileName   = fileName   === undefined ? err.fileName   : fileName;
    this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}

MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';

5

正如其他人所说,在Node中,它很简单:

class DumbError extends Error {
    constructor(foo = 'bar', ...params) {
        super(...params);

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, DumbError);
        }

        this.name = 'DumbError';

        this.foo = foo;
        this.date = new Date();
    }
}

try {
    let x = 3;
    if (x < 10) {
        throw new DumbError();
    }
} catch (error) {
    console.log(error);
}

3

我只想补充别人已经说过的话:

为确保自定义错误类在堆栈跟踪中正确显示,您需要将自定义错误类的原型的name属性设置为自定义错误类的name属性。这就是我的意思:

CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';

因此,完整的示例将是:

    var CustomError = function(message) {
        var err = new Error(message);
        err.name = 'CustomError';
        this.name = err.name;
        this.message = err.message;
        //check if there is a stack property supported in browser
        if (err.stack) {
            this.stack = err.stack;
        }
        //we should define how our toString function works as this will be used internally
        //by the browser's stack trace generation function
        this.toString = function() {
           return this.name + ': ' + this.message;
        };
    };
    CustomError.prototype = new Error();
    CustomError.prototype.name = 'CustomError';

一切都说完了之后,您就抛出了新的异常,它看起来像这样(我在chrome dev工具中偷懒地尝试过):

CustomError: Stuff Happened. GASP!
    at Error.CustomError (<anonymous>:3:19)
    at <anonymous>:2:7
    at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
    at Object.InjectedScript.evaluate (<anonymous>:481:21)

5
这不会覆盖所有错误实例的名称属性吗?
panzi 2014年

@panzi你是正确的。我已经修复了我的小错误。感谢您的注意!
Gautham C.

3

我的2美分:

为什么还要另一个答案?

a)因为访问Error.stack属性(如在某些答案中)会带来很大的性能损失。

b)因为只有一行。

c)因为https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error上的解决方案似乎并未保留堆栈信息。

//MyError class constructor
function MyError(msg){
    this.__proto__.__proto__ = Error.apply(null, arguments);
};

使用范例

http://jsfiddle.net/luciotato/xXyeB/

它有什么作用?

this.__proto__.__proto__MyError.prototype.__proto__,因此将__proto__MyError 的FOR ALL INSTANCES 设置为特定的新创建的Error。它保留MyError类的属性和方法,还将新的Error属性(包括.stack)放入__proto__链中。

明显的问题:

具有有用的堆栈信息的MyError实例不能超过一个。

如果您不完全了解this.__proto__.__proto__=它,请不要使用此解决方案。


2

由于很难对JavaScript异常进行子类化,因此我不对它进行子类化。我只是创建一个新的Exception类,并在其中使用Error。我更改了Error.name属性,使其看起来像我在控制台上的自定义异常:

var InvalidInputError = function(message) {
    var error = new Error(message);
    error.name = 'InvalidInputError';
    return error;
};

可以像常规错误一样抛出上述新异常,并且该异常将按预期运行,例如:

throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string 

注意:堆栈跟踪不是完美的,因为它将使您到达创建新错误的位置,而不是引发错误的位置。在Chrome上这不是什么大问题,因为它可以直接在控制台中提供完整的堆栈跟踪。但这在Firefox上更成问题。


在这种情况下,此操作将失败m = new InvalidInputError(); dontThrowMeYet(m);
Eric

@Eric我同意,但这似乎是一个很小的限制。我从来不需要提前实例化异常对象(除了像上面的代码示例这样的元编程用途)。这真的对您有问题吗?
乔纳森·本

是的,行为似乎是相同的,所以我将更改答案。我对堆栈跟踪不是100%满意,它使您进入Firefox和Chrome上的“ var error”行
Jonathan Benn 2014年

1
@JonathanBenn我参加聚会真的很晚,所以也许您已经选择了这个。使用异步编程和Promises时,我经常实例化异常对象。跟随@Eric的名字,我经常使用m = new ...then Promise.reject(m)。没必要,但是代码更易于阅读。
BaldEagle,2016年

1
@JonathanBenn :(他)在10月14日,您似乎认为在抛出异常之前实例化异常对象的情况很少见。我举了一个例子,我做了一次。我不会说这很普遍,但是在我想要的时候拥有它很方便。而且,我的代码更具可读性,因为实例化全部在一行上而拒绝全部在另一行上。我希望能做到!
BaldEagle,2013年

2

正如Mohsen的答案所指出的那样,在ES6中可以使用类来扩展错误。这要容易得多,并且它们的行为与本机错误更加一致...但是不幸的是,如果需要支持ES6之前的浏览器,在浏览器中使用它并不是一件简单的事情。有关如何实现的一些说明,请参见下文,但与此同时,我建议一种相对简单的方法,该方法结合了其他答案中的一些最佳建议:

function CustomError(message) {
    //This is for future compatibility with the ES6 version, which
    //would display a similar message if invoked without the
    //`new` operator.
    if (!(this instanceof CustomError)) {
        throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
    }
    this.message = message;

    //Stack trace in V8
    if (Error.captureStackTrace) {
       Error.captureStackTrace(this, CustomError);
    }
    else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';

在ES6中,它很简单:

class CustomError extends Error {}

...并且您可以使用来检测对ES6类的支持try {eval('class X{}'),但是如果尝试在旧版浏览器加载的脚本中包含ES6版本,则会出现语法错误。因此,支持所有浏览器的唯一方法是为eval()支持ES6的浏览器动态加载单独的脚本(例如,通过AJAX或)。更为复杂的是,eval()并非所有环境都支持(由于内容安全策略),这可能会或可能不会成为您项目的考虑因素。

因此,就目前而言,无论是上面的第一种方法,还是直接使用Error而不尝试扩展它,对于需要支持非ES6浏览器的代码,实际上似乎是最好的方法。

有些人可能要考虑另一种方法,即使用Object.setPrototypeOf()可用的位置创建一个错误对象,该对象是您的自定义错误类型的实例,但其外观和行为更像是控制台中的本机错误(感谢Ben的回答)的建议)。这是我对这种方法的看法:https : //gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8。但是考虑到有一天我们将只能使用ES6,就我个人而言,我不确定这种方法的复杂性是否值得。


1

做到这一点的方法是从构造函数返回apply的结果,并以通常的复杂javascript方式设置原型:

function MyError() {
    var tmp = Error.apply(this, arguments);
    tmp.name = this.name = 'MyError'

    this.stack = tmp.stack
    this.message = tmp.message

    return this
}
    var IntermediateInheritor = function() {}
        IntermediateInheritor.prototype = Error.prototype;
    MyError.prototype = new IntermediateInheritor()

var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error)                // true
console.log(myError instanceof MyError)              // true
console.log(myError.toString())                      // MyError: message
console.log(myError.stack)                           // MyError: message \n 
                                                     // <stack trace ...>

在这一点上,这种方式(我已经对其进行了一些迭代)的唯一问题是

  • stackmessage中不包含MyError和的其他属性
  • stacktrace还有另一行,这并不是真正必要的。

第一个问题可以通过使用此答案中的技巧遍历错误的所有不可枚举的属性来解决:是否可以获取对象的不可枚举的继承属性名称?,但是ie <9不支持此功能。第二个问题可以通过在堆栈跟踪中删除该行来解决,但是我不确定如何安全地执行此操作(也许只是删除e.stack.toString()的第二行)。


我制作了一个可以扩展大多数常规旧javascript对象(包括错误)的模块。此时,它已经相当成熟了github.com/fresheneesz/proto
英国电信

1

该代码段显示了所有内容。

function add(x, y) {
      if (x && y) {
        return x + y;
      } else {
        /**
         * 
         * the error thrown will be instanceof Error class and InvalidArgsError also
         */
        throw new InvalidArgsError();
        // throw new Invalid_Args_Error(); 
      }
    }

    // Declare custom error using using Class
    class Invalid_Args_Error extends Error {
      constructor() {
        super("Invalid arguments");
        Error.captureStackTrace(this);
      }
    }

    // Declare custom error using Function
    function InvalidArgsError(message) {
      this.message = `Invalid arguments`;
      Error.captureStackTrace(this);
    }
    // does the same magic as extends keyword
    Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);

    try{
      add(2)
    }catch(e){
      // true
      if(e instanceof Error){
        console.log(e)
      }
      // true
      if(e instanceof InvalidArgsError){
        console.log(e)
      }
    }

0

我会退后一步,考虑为什么要这么做?我认为关键是要以不同的方式处理不同的错误。

例如,在Python中,您可以将catch语句限制为仅catch MyValidationError,也许您希望能够在javascript中执行类似的操作。

catch (MyValidationError e) {
    ....
}

您无法使用javascript执行此操作。只会有一个捕获块。您应该对错误使用if语句来确定其类型。

catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }

我想我会抛出一个带有类型,消息和您认为合适的其他属性的原始对象。

throw { type: "validation", message: "Invalid timestamp" }

当您发现错误时:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}

1
扔一个物体不是一个好主意。您没有error.stack,标准工具将无法使用,等等,等等。一种更好的方法是将属性添加到错误实例,例如var e = new Error(); e.type = "validation"; ...
timruffles

0

自定义错误装饰器

这是基于乔治·贝利的回答,但扩展并简化了最初的想法。它是用CoffeeScript编写的,但是很容易转换为JavaScript。这个想法是通过包装它的装饰器扩展Bailey的自定义错误,使您可以轻松地创建新的自定义错误。

注意:这仅在V8中有效。Error.captureStackTrace在其他环境中不支持。

限定

装饰器采用错误类型的名称,然后返回采用错误消息的函数并将其括起来。

CoreError = (@message) ->

    @constructor.prototype.__proto__ = Error.prototype
    Error.captureStackTrace @, @constructor
    @name = @constructor.name

BaseError = (type) ->

    (message) -> new CoreError "#{ type }Error: #{ message }"

采用

现在很容易创建新的错误类型。

StorageError   = BaseError "Storage"
SignatureError = BaseError "Signature"

为了好玩,您现在可以定义一个函数,SignatureError如果调用了过多的args,则该函数将抛出a 。

f = -> throw SignatureError "too many args" if arguments.length

这已经过很好的测试,并且似乎可以在V8上完美运行,保持回溯,位置等。

注意:new构造自定义错误时,使用是可选的。


0

如果您不关心错误的表现,这是您可以做的最小的事情

Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
    const error = new Error(message)
    Object.setPrototypeOf(error, MyError.prototype);
    return error
}

您可以使用它而无需新的MyError(message)

通过在调用构造函数Error之后更改原型,我们不必设置调用堆栈和消息


0

Mohsen在ES6上面有一个很好的答案,它设置了名称,但是如果您使用的是TypeScript,或者您生活在将来,希望有关公共和私有类字段的提案已超过第3阶段的提案,并提出了此建议,进入第4阶段(作为ECMAScript / JavaScript的一部分),那么您可能想知道它会短一些。第3阶段是浏览器开始实现功能的位置,因此,如果您的浏览器支持,则下面的代码可能会起作用。(在新的Edge浏览器v81中进行了测试,似乎工作正常)。请注意,尽管此功能目前尚不稳定,应谨慎使用,并且应始终检查浏览器对不稳定功能的支持。这篇文章主要针对浏览器可能支持的未来居民。要检查支持,请检查MDN我可以使用。它目前有跨浏览器的市场这是越来越有,但不是很大,所以如果你真的想现在使用它,不想等待或者使用像transpiler 66%的支持巴别塔之类的东西打字稿

class EOFError extends Error { 
  name="EOFError"
}
throw new EOFError("Oops errored");

将此错误与无名错误进行比较,该错误在引发时将不会记录其名称。

class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.