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

IT技术 javascript error-handling
2021-02-25 04:03:00

您将如何实现不同类型的错误,以便您能够捕获特定的错误并让其他人冒泡……?

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

Error.prototype.sender = "";


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

    e.sender = "specific";

    throw e;
}

捕获特定错误:

try
{
    throwSpecificError();
}

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

    // handle specific error
}


大家有没有什么替代品?

6个回答

要创建自定义异常,您可以从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
  }
}

一种不继承自 的简约方法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
  }
}
继承Error有问题。参见stackoverflow.com/questions/1382107/...
2021-04-22 04:03:00
这段代码的问题} catch (e) { if (e.name == 'SpecificError') { // specific error } else { throw e; // let others bubble up } }是:它在 IE7 中不起作用,引发“异常抛出但未捕获”错误。以下是来自 msdn 的极其愚蠢(一如既往)的解释:“您包含了一个 throw 语句,但它没有包含在 try 块中,或者没有关联的 catch 块来捕获错误。从 try 块中抛出异常使用 throw 语句,并使用 catch 语句在 try 块之外捕获。”
2021-05-01 04:03:00
@LuisNell,如果您仔细查看我的代码示例,您会发现我并未建议使用name构造函数属性。我建议扔一个带有name属性的定制对象,它不会破坏......
2021-05-06 04:03:00
嗯,微软的 C# 肯定比 Javascript 处理错误更好:P。Mozzilla 在 Firefox 中添加了类似的东西。虽然它不在 Ecmascript 标准中,甚至不在 ES6 中,但他们也解释了如何使其符合,尽管它不是那么简洁。与上面基本相同,但使用instanceOf. 在这里查看
2021-05-10 04:03:00
在 Javascript 中,你可以抛出任何你想要的东西,可以是一个简单的字符串、一个数字(想想错误代码)或一个完全限定的对象。甜的!
2021-05-14 04:03:00

正如下面的评论中所指出的,这是 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;
} 

这提供了一些更类似于 Java 中使用的类型化异常处理的东西,至少在语法上是这样。

是的,这是一个仅限 Mozilla 的扩展,甚至不建议用于标准化。作为语法级别的功能,无法嗅探它并选择使用它。
2021-04-17 04:03:00
另外,关于提议的解决方案是非标准的。引用形式 [Mozilla 的 JavaScript 参考 [( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ):This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.
2021-04-18 04:03:00
仅受 Firefox 支持(自 2.0 起)。它甚至无法在其他浏览器中解析;你只会得到语法错误。
2021-04-27 04:03:00
条件捕获是我之前不知道或忘记的东西。谢谢你教育/提醒我!+1
2021-05-07 04:03:00
结合CMS的答案,它是完美的。
2021-05-12 04:03:00

try-catch-finally.js

使用try-catch-finally.js,您可以_try使用匿名回调调用该函数,它将调用该函数,您可以链接.catch调用以捕获特定错误,并.finally调用以任何一种方式执行。

例子

_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');
});

带有现代箭头函数和模板文字的示例

_try(() => {
  throw 'My error';
}).catch(Error, e => {
  console.log(`Caught Error: ${e}`);
}).catch(String, e => {
  console.log(`Caught String: ${e}`);
}).catch(e => {
  console.log(`Caught other: ${e}`);
}).finally(() => {
  console.log('Error was caught explicitly');
});

导出使用module

/**
 * Custom InputError
 */
class InputError extends Error {
  /**
   * Create InputError
   * @param {String} message
   */
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    Error.captureStackTrace(this, this.constructor);
  }
}

/**
 * Custom AuthError
 */
class AuthError extends Error {
  /**
   * Create AuthError
   * @param {String} message
   */
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    Error.captureStackTrace(this, this.constructor);
  }
}

/**
 * Custom NotFoundError
 */
class NotFoundError extends Error {
  /**
   * Create NotFoundError
   * @param {String} message
   */
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = {
  InputError: InputError,
  AuthError: AuthError,
  NotFoundError: NotFoundError
};

导入脚本:

const {InputError, AuthError, NotFoundError} = require(path.join(process.cwd(), 'lib', 'errors'));

利用:

function doTheCheck = () =>
  checkInputData().then(() => {
    return Promise.resolve();
  }).catch(err => {
    return Promise.reject(new InputError(err));
  });
};

外部调用代码:

doTheCheck.then(() => {
  res.send('Ok');
}).catch(err => {
  if (err instanceof NotFoundError) {
    res.status(404).send('Not found');
  } else if (err instanceof AuthError) {
    res.status(301).send('Not allowed');
  } else if (err instanceof InputError) {
    res.status(400).send('Input invalid');
  } else {
    console.error(err.toString());
    res.status(500).send('Server error');
  }
});

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

1.继承Error类

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

class FileNotFoundException extends Error {
  constructor(message) {
    super(message)
    // Not required, but makes uncaught error messages nicer.
    this.name = 'FileNotFoundException'
  }
}

完整示例:

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

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

有很多关于如何正确地子类化的讨论Error例如,如果您使用的是转译器,则上述解决方案可能不起作用。有些人建议使用特定于平台的 captureStackTrace() 如果它可用(虽然我在使用它时没有注意到错误有任何不同 - 也许它不再相关了🤷‍♂️)。要了解更多信息,请参阅MDN 页面和Stackoverflow 答案。

许多浏览器 API 走这条路并抛出自定义异常(可以在这里看到

2. 给Error增加一个区别属性

这个想法很简单。创建您的错误,为您的错误添加一个额外的属性,例如“代码”,然后抛出它。

const error = new Error(`The file ${path} was not found`)
error.code = 'NotFound'
throw error

完整示例:

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

此解决方案的优点是您无需导出包可能引发的所有可能异常的列表。您可以想象,例如,如果您的包一直使用 NotFound 异常来指示特定函数无法找到预期资源,那会变得多么尴尬。您想要添加一个 addUserToGroup() 函数,该函数理想情况下会根据未找到的资源抛出 UserNotFound 或 GroupNotFound 异常。对于子类异常,您将不得不做出棘手的决定。使用错误对象上的代码,您就可以做到。

这是路由节点的 fs module对异常的处理。如果您试图读取一个不存在的文件,它会抛出一个带有一些附加属性的错误实例,例如code,它将"ENOENT"为该特定异常设置为。

3. 返回您的异常。

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

function readFile(path) {
  if (itFailed()) {
    return { exCode: 'NotFound' }
  } else {
    return { data: 'Contents of file' }
  }
}

在处理大量异常时,像这样的解决方案可能最有意义。这很容易做到,并且可以帮助自我记录哪些函数给出了哪些异常,这使得代码更加健壮。缺点是它会给你的代码增加很多膨胀。

完整示例:

非解决方案

其中一些解决方案感觉需要做很多工作。只是抛出一个对象文字很诱人,例如throw { code: 'NotFound' }. 不要这样做!堆栈跟踪信息附加到错误对象。如果这些对象文字中的一个曾经漏过并成为未捕获的异常,您将无法通过堆栈跟踪来知道它在哪里或如何发生。一般来说,调试会困难得多。

奖励:显式异常

当您的包开始处理大量异常时,我建议使用如上所述的“返回异常”方法,以帮助您跟踪哪些异常可能来自何处(至少在您的包内部使用它 - 您仍然可以为您的包裹用户扔东西)。有时,即使是这种解决方案也不够好。我整理了一些显式异常包来帮助处理这些场景(可以在此处找到。还有一个轻量版本,您可以在此处复制粘贴到您的项目中)。这个想法是要求用户明确列出他们希望函数调用提供哪些异常。这使得跟踪异常路径变得非常容易。

try {
  // readFile() will give an exception.
  // In unwrap(), you list which exceptions you except, to help with self-documentation.
  // runtime checks help ensure no other kinds of exceptions slip through.
  return unwrap(readFile('./example.txt'), ['NotFound'])
} catch (err) {
  // Handle the exception here
}

完整示例: