处理JavaScript中的特定错误(想想异常)

cll*_*pse 98 javascript error-handling

你将如何实现不同类型的错误,这样你就能捕捉到特定错误并让其他人冒出来......?

实现此目的的一种方法是修改Error对象的原型:

Error.prototype.sender = "";


function throwSpecificError()
{
    var e = new Error();

    e.sender = "specific";

    throw e;
}
Run Code Online (Sandbox Code Playgroud)

捕获特定错误:

try
{
    throwSpecificError();
}

catch (e)
{
    if (e.sender !== "specific") throw e;

    // handle specific error
}
Run Code Online (Sandbox Code Playgroud)


你有没有其他选择?

CMS*_*CMS 134

要创建自定义异常,您可以从Error对象继承:

function SpecificError () {

}

SpecificError.prototype = new Error();

// ...
try {
  throw new SpecificError;
} catch (e) {
  if (e instanceof SpecificError) {
   // specific error
  } else {
    throw e; // let others bubble up
  }
}
Run Code Online (Sandbox Code Playgroud)

没有继承自Error的简约方法可能是抛出一个具有名称和消息属性的简单对象:

function throwSpecificError() {
  throw {
    name: 'SpecificError',
    message: 'SpecificError occurred!'
  };
}


// ...
try {
  throwSpecificError();
} catch (e) {
  if (e.name == 'SpecificError') {
   // specific error
  } else {
    throw e; // let others bubble up
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这段代码的问题:`} catch(e){if(e.name =='SpecificError'){//特定错误} else {throw e; //让其他人冒泡}}`是它在IE7中不起作用,引发了"Exception thrown and not caught"错误.以下是来自msdn的极其愚蠢(一如既往)的解释:"你包含了一个throw语句,但它没有包含在try块中,或者没有相关的catch块来捕获错误.在try块中引发了异常使用throw语句,并使用catch语句捕获try块外部." (4认同)
  • 从 ES6 开始,您可以使用“class SpecificError extends Error {}”。 (3认同)
  • 继承"错误"有问题.请参阅http://stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript/1382129#1382129 (2认同)

Sco*_*son 16

遗憾的是,没有“官方”方法可以在 Javascript 中实现此基本功能。我将分享我在不同软件包中看到的三种最常见的解决方案,以及如何在现代 Javascript (es6+) 中实现它们,以及它们的一些优点和缺点。

\n

1. 对 Error 类进行子类化

\n

在 es6 中,对“Error”实例进行子类化变得更加容易。只需执行以下操作:

\n
class FileNotFoundException extends Error {\n  constructor(message) {\n    super(message);\n    // Not required, but makes uncaught error messages nicer.\n    this.name = \'FileNotFoundException\';\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

完整示例:

\n

\r\n
\r\n
class FileNotFoundException extends Error {\n  constructor(message) {\n    super(message);\n    // Not required, but makes uncaught error messages nicer.\n    this.name = \'FileNotFoundException\';\n  }\n}\n\n// Example usage\n\nfunction readFile(path) {\n  throw new FileNotFoundException(`The file ${path} was not found`);\n}\n\ntry {\n  readFile(\'./example.txt\');\n} catch (err) {\n  if (err instanceof FileNotFoundException) {\n    // Handle the custom exception\n    console.log(`Could not find the file. Reason: ${err.message}`);\n  } else {\n    // Rethrow it - we don\'t know how to handle it\n    // The stacktrace won\'t be changed, because\n    // that information is attached to the error\n    // object when it\'s first constructed.\n    throw err;\n  }\n}
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

如果您不喜欢设置this.name为硬编码字符串,则可以将其设置为this.constructor.name,这将给出您的类的名称。这样做的优点是您的自定义异常的任何子类都不需要更新this.namethis.constructor.name子类的名称也是如此。

\n

与某些替代解决方案相比,子类化异常的优点是可以提供更好的编辑器支持(例如自动完成)。您可以轻松地将自定义行为添加到特定的异常类型,例如附加函数、替代构造函数参数等。在提供自定义行为或数据时,它也往往更容易支持 typescript。

\n

关于如何正确子类化有很多讨论Error。例如,如果您使用转译器,上述解决方案可能不起作用。有些人建议使用特定于平台的 captureStackTrace() (如果可用的话)(不过,当我使用它时,我没有注意到错误有任何区别 - 也许它不再那么相关了 \xe2\x80\x8d\ xe2\x99\x82\xef\xb8\x8f)。要了解更多信息,请参阅MDN 页面和Stackoverflow 答案。

\n

许多浏览器 API 都采用这种方式并抛出自定义异常(如此处所示

\n

请注意,如果您要转译为 es5,则此解决方案可能不起作用。在 Babel 版本 6 中,instanceof根本无法使用子类内置函数。在版本 7 中,他们仍然警告内置子类化可能会导致意外问题。像这样 100% 准确地转译代码是不可能的,因此所有转译器都必须在不同的权衡之间进行选择。

\n

2.为Error添加一个区分属性

\n

这个想法非常简单。创建错误,向错误添加额外的属性(例如“代码”),然后抛出它。

\n
const error = new Error(`The file ${path} was not found`);\nerror.code = \'NotFound\';\nthrow error;\n
Run Code Online (Sandbox Code Playgroud)\n

完整示例:\n

\r\n
\r\n
function readFile(path) {\n  const error = new Error(`The file ${path} was not found`);\n  error.code = \'NotFound\';\n  throw error;\n}\n\ntry {\n  readFile(\'./example.txt\');\n} catch (err) {\n  if (err.code === \'NotFound\') {\n    console.log(`Could not find the file. Reason: ${err.message}`);\n  } else {\n    throw err;\n  }\n}
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

当然,您可以创建一个辅助函数来删除一些样板文件并确保一致性。

\n

此解决方案的优点是您不需要导出包可能引发的所有可能异常的列表。您可以想象,例如,如果您的包一直使用 NotFound 异常来指示特定函数无法找到预期的资源,那么情况会变得多么尴尬。您想要添加一个 addUserToGroup() 函数,理想情况下该函数会根据未找到的资源抛出 UserNotFound 或 GroupNotFound 异常。对于子类化的异常,您将面临一个棘手的决定(您是否应该添加两个新的错误类,只是为了这个函数?或者也许在 NotFoundError 类上添加一个可选属性,提供有关未找到内容的更多信息?)。通过错误对象上的代码,您就可以做到这一点。

\n

这是路由节点的 fs 模块处理异常的方法。如果您尝试读取不存在的文件,它将抛出一个Error带有一些附加属性的实例,例如code,它将设置为"ENOENT"为该实例。

\n

3. 返回您的异常。

\n

谁说你必须扔掉它们?在某些情况下,返回出错的地方可能是最有意义的。

\n
function readFile(path) {\n  if (itFailed()) {\n    return { exCode: \'NotFound\' };\n  } else {\n    return { data: \'Contents of file\' };\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当处理大量异常时,这样的解决方案可能是最有意义的。这样做很简单,并且可以帮助自我记录哪些函数给出哪些异常,从而使代码更加健壮。缺点是它会给你的代码带来很多臃肿。

\n

完整示例:\n

\r\n
\r\n
function readFile(path) {\n  if (Math.random() > 0.5) {\n    return { exCode: \'NotFound\' };\n  } else {\n    return { data: \'Contents of file\' };\n  }\n}\n\nfunction main() {\n  const { data, exCode } = readFile(\'./example.txt\');\n\n  if (exCode === \'NotFound\') {\n    console.log(\'Could not find the file.\');\n    return;\n  } else if (exCode) {\n    // We don\'t know how to handle this exCode, so throw an error\n    throw new Error(`Unhandled exception when reading file: ${exCode}`);\n  }\n\n  console.log(`Contents of file: ${data}`);\n}\nmain();
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

非解决方案

\n

其中一些解决方案感觉工作量很大。很容易只抛出一个对象文字,例如throw { code: \'NotFound\' }不要这样做!堆栈跟踪信息附加到错误对象。如果这些对象文字之一漏掉并成为未捕获的异常,您将无法通过堆栈跟踪来了解它在何处或如何发生。一般来说,调试会困难得多。如果这些对象之一未被捕获,某些浏览器可能会在控制台中显示堆栈跟踪,但这只是它们提供的一种可选便利,并非所有平台都提供这种便利,并且它并不总是准确的,例如,如果该对象被捕获并且重新抛出浏览器可能会给出错误的堆栈跟踪。

\n

即将推出的解决方案

\n

JavaScript 委员会正在研究一些提案,这些提案将使异常处理变得更好用。这些提案如何运作的细节仍在不断变化,并且正在积极讨论,因此在事情稳定下来之前我不会深入讨论太多细节,但这里是对即将发生的事情的粗略品味:

\n

即将到来的最大变化将是模式匹配提案,其目的是成为一个更好的“开关”等。有了它,您就可以轻松地使用简单的语法来匹配不同类型的错误。

\n

下面是这可能的样子:

\n
try {\n  ...\n} catch (err) {\n  match (err) {\n    // Checks if `err` is an instance of UserNotFound\n    when (${UserNotFound}): console.error(\'The user was not found!\');\n\n    // Checks if it has a correct code property set to "ENOENT"\n    when ({ code: \'ENOENT\' }): console.error(\'...\');\n\n    // Handles everything else\n    else: throw err;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

与返回异常路由的模式匹配使您能够以与函数式语言中通常完成的方式非常相似的方式进行异常处理。唯一缺少的是“任一”类型,但 TypeScript 联合类型可以发挥非常相似的作用。

\n
const result = match (divide(x, y)) {\n  // (Please refer to the proposal for a more in-depth\n  // explanation of this syntax)\n  when ({ type: \'RESULT\', value }): value + 1\n  when ({ type: \'DivideByZero\' }): -1\n}\n
Run Code Online (Sandbox Code Playgroud)\n

还有一些非常早期的讨论,关于将这种模式匹配语法直接引入到 try-catch 语法中,以允许您执行类似于此的操作:

\n
try {\n  doSomething();\n} CatchPatten (UserNotFoundError & err) {\n  console.error(\'The user was not found! \' + err);\n} CatchPatten ({ type: \'ENOENT\' }) {\n  console.error(\'File not found!\');\n} catch (err) {\n  throw err;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

更新

\n

对于那些需要一种方法来自我记录哪些函数抛出哪些异常,以及确保这种自我记录保持诚实的方法的人,我之前在这个答案中推荐了一个小包,以帮助您跟踪给定的异常函数可能会抛出异常。虽然这个包可以完成这项工作,但现在我只是建议使用 TypeScript 和“返回异常”路线,以获得最大的异常安全性。借助 TypeScript 联合类型,您可以轻松记录特定函数将返回哪些异常,并且 TypeScript 可以帮助您保持此文档的真实性,并在出现问题时向您提供类型错误。

\n


And*_*ndy 14

如下面的评论中所述,这是特定于Mozilla的,但您可以使用"条件捕获"块.例如:

try {
  ...
  throwSpecificError();
  ...
}
catch (e if e.sender === "specific") {
  specificHandler(e);
}
catch (e if e.sender === "unspecific") {
  unspecificHandler(e);
}
catch (e) {
  // don't know what to do
  throw e;
} 
Run Code Online (Sandbox Code Playgroud)

这给出了更类似于Java中使用的类型化异常处理的东西,至少在语法上如此.

  • 仅支持Firefox(自2.0起).它甚至没有在其他浏览器中解析; 你只会得到语法错误. (12认同)
  • 是的,这是一个仅限Mozilla的扩展,它甚至不建议用于标准化.作为语法级功能,无法嗅探它并可选择使用它. (10认同)
  • 条件捕获是我之前不知道或忘记的事情.感谢您的教育/提醒我!+1 (3认同)
  • 另外,关于提出的解决方案是非标准的.引用形式[Mozilla的JavaScript参考[(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch#Conditional_catch_clauses):`这个功能是非标准的,不符合标准.不要在面向Web的生产站点上使用它:它不适用于每个用户.实现之间可能存在很大的不兼容性,并且行为可能在将来发生变化 (3认同)

c24*_*24w 10

try-catch-finally.js

_try(function () {
    throw 'My error';
})
.catch(Error, function (e) {
    console.log('Caught Error: ' + e);
})
.catch(String, function (e) {
    console.log('Caught String: ' + e);
})
.catch(function (e) {
    console.log('Caught other: ' + e);
})
.finally(function () {
    console.log('Error was caught explicitly');
});
Run Code Online (Sandbox Code Playgroud)