throwtry 语句try 代码块catch 子句finally 子句Error 及其子类ErrorError 的内置子类Errorerror.cause 链接错误 [ES2022].cause 的替代方案:自定义错误类本章介绍 JavaScript 如何处理异常。
为什么 JavaScript 不更频繁地抛出异常?
JavaScript 直到 ES3 才支持异常。这解释了为什么该语言及其标准库很少使用它们。
考虑以下代码。它将存储在文件中的配置文件读取到一个包含 Profile 类实例的数组中
function readProfiles(filePaths) {
const profiles = [];
for (const filePath of filePaths) {
try {
const profile = readOneProfile(filePath);
profiles.push(profile);
} catch (err) { // (A)
console.log('Error in: '+filePath, err);
}
}
}
function readOneProfile(filePath) {
const profile = new Profile();
const file = openFile(filePath);
// ··· (Read the data in `file` into `profile`)
return profile;
}
function openFile(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error('Could not find file '+filePath); // (B)
}
// ··· (Open the file whose path is `filePath`)
}让我们检查一下在 B 行发生了什么:发生了一个错误,但处理该问题的最佳位置不是当前位置,而是 A 行。在那里,我们可以跳过当前文件并继续处理下一个文件。
因此
throw 语句来指示出现了问题。try-catch 语句来处理问题。当我们抛出异常时,以下结构是活动的
readProfiles(···)
for (const filePath of filePaths)
try
readOneProfile(···)
openFile(···)
if (!fs.existsSync(filePath))
throw
throw 会逐个退出嵌套结构,直到遇到 try 语句。执行在该 try 语句的 catch 子句中继续。
throw这是 throw 语句的语法
throw «value»;在 JavaScript 中可以抛出任何值。但是,最好使用 Error 的实例或子类,因为它们支持其他功能,例如堆栈跟踪和错误链接(请参阅 §24.4 “Error 及其子类”)。
这给我们留下了以下选项
直接使用类 Error。这在 JavaScript 中比在更静态的语言中限制更少,因为我们可以向实例添加自己的属性
const err = new Error('Could not find the file');
err.filePath = filePath;
throw err;使用 Error 的子类 之一。
子类化 Error(稍后 将解释更多详细信息)
class MyError extends Error {
}
function func() {
throw new MyError('Problem!');
}
assert.throws(
() => func(),
MyError);try 语句try 语句的最大版本如下所示
try {
«try_statements»
} catch (error) {
«catch_statements»
} finally {
«finally_statements»
}我们可以组合这些子句,如下所示
try-catchtry-finallytry-catch-finallytry 代码块try 代码块可以被视为语句的主体。这是我们执行常规代码的地方。
catch 子句如果异常到达 try 代码块,则将其分配给 catch 子句的参数,并执行该子句中的代码。接下来,执行通常在 try 语句之后继续。如果出现以下情况,则可能会发生变化
catch 代码块内有 return、break 或 throw。finally 子句(它总是在 try 语句结束之前执行)。以下代码演示了在 A 行抛出的值确实在 B 行被捕获。
const errorObject = new Error();
function func() {
throw errorObject; // (A)
}
try {
func();
} catch (err) { // (B)
assert.equal(err, errorObject);
}catch 绑定 [ES2019]如果我们对抛出的值不感兴趣,我们可以省略 catch 参数
try {
// ···
} catch {
// ···
}这有时可能很有用。例如,Node.js 具有 API 函数 assert.throws(func),用于检查 func 内部是否抛出错误。它可以实现如下。
function throws(func) {
try {
func();
} catch {
return; // everything OK
}
throw new Error('Function didn’t throw an exception!');
}但是,此函数的更完整的实现将具有 catch 参数,并且例如会检查其类型是否符合预期。
finally 子句finally 子句中的代码总是在 try 语句结束时执行,无论 try 代码块或 catch 子句中发生什么。
让我们看一个 finally 的常见用例:我们创建了一个资源,并且希望在使用完它后始终销毁它,无论在使用它时发生什么。我们将按如下方式实现
const resource = createResource();
try {
// Work with `resource`. Errors may be thrown.
} finally {
resource.destroy();
}finally 始终执行finally 子句始终执行,即使抛出错误(A 行)
let finallyWasExecuted = false;
assert.throws(
() => {
try {
throw new Error(); // (A)
} finally {
finallyWasExecuted = true;
}
},
Error
);
assert.equal(finallyWasExecuted, true);即使有 return 语句(A 行)
let finallyWasExecuted = false;
function func() {
try {
return; // (A)
} finally {
finallyWasExecuted = true;
}
}
func();
assert.equal(finallyWasExecuted, true);Error 及其子类Error 是所有内置错误类的公共超类。
Error这就是 Error 的实例属性和构造函数的样子
class Error {
// Instance properties
message: string;
cause?: any; // ES2022
stack: string; // non-standard but widely supported
constructor(
message: string = '',
options?: ErrorOptions // ES2022
);
}
interface ErrorOptions {
cause?: any; // ES2022
}构造函数有两个参数
message 指定错误消息。options 是在 ECMAScript 2022 中引入的。它包含一个对象,其中当前支持一个属性.cause 指定哪个异常(如果有)导致了当前错误。接下来的小节之后的子节将更详细地解释实例属性 .message、.cause 和 .stack。
Error.prototype.name每个内置错误类 E 都有一个属性 E.prototype.name
> Error.prototype.name
'Error'
> RangeError.prototype.name
'RangeError'因此,有两种方法可以获取内置错误对象的类名
> new RangeError().name
'RangeError'
> new RangeError().constructor.name
'RangeError'.message.message 仅包含错误消息
const err = new Error('Hello!');
assert.equal(String(err), 'Error: Hello!');
assert.equal(err.message, 'Hello!');如果我们省略消息,则使用空字符串作为默认值(继承自 Error.prototype.message)
如果我们省略消息,则它是空字符串
assert.equal(new Error().message, '');.stack实例属性 .stack 不是 ECMAScript 功能,但它得到 JavaScript 引擎的广泛支持。它通常是一个字符串,但其确切结构没有标准化,并且在不同的引擎之间有所不同。
这就是它在 JavaScript 引擎 V8 上的样子
const err = new Error('Hello!');
assert.equal(
err.stack,
`
Error: Hello!
at file://ch_exception-handling.mjs:1:13
`.trim());.cause [ES2022]实例属性 .cause 是通过 new Error() 的第二个参数中的 options 对象创建的。它指定了哪个其他错误导致了当前错误。
const err = new Error('msg', {cause: 'the cause'});
assert.equal(err.cause, 'the cause');有关如何使用此属性的信息,请参阅 §24.5 “链接错误”。
Error 的内置子类Error 具有以下子类 - 引用 ECMAScript 规范
AggregateError [ES2021] 表示多个同时发生的错误。在标准库中,只有 Promise.any() 使用它。RangeError 表示一个值不在允许值的集合或范围内。ReferenceError 表示检测到无效的引用值。SyntaxError 表示发生了解析错误。TypeError 用于指示操作失败,而其他任何 NativeError 对象都不足以指示失败原因。URIError 表示以与其定义不兼容的方式使用了全局 URI 处理函数之一。Error自 ECMAScript 2022 起,Error 构造函数接受两个参数(请参阅上一小节)。因此,我们在对其进行子类化时有两个选择:我们可以在子类中省略构造函数,也可以像这样调用 super()
class MyCustomError extends Error {
constructor(message, options) {
super(message, options);
// ···
}
}有时,我们会捕获在更深层嵌套的函数调用期间抛出的错误,并希望向其附加更多信息
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
const text = readText(filePath);
const json = JSON.parse(text);
return processJson(json);
} catch (error) {
// (A)
}
});
}try 子句中的语句可能会抛出各种错误。在大多数情况下,错误不会意识到导致它的文件的路径。这就是为什么我们想在 A 行附加该信息。
error.cause 链接错误 [ES2022]自 ECMAScript 2022 起,new Error() 允许我们指定导致它的原因
function readFiles(filePaths) {
return filePaths.map(
(filePath) => {
try {
// ···
} catch (error) {
throw new Error(
`While processing ${filePath}`,
{cause: error}
);
}
});
}.cause 的替代方案:自定义错误类以下自定义错误类支持链接。它与 .cause 向前兼容。
/**
* An error class that supports error chaining.
* If there is built-in support for .cause, it uses it.
* Otherwise, it creates this property itself.
*
* @see https://github.com/tc39/proposal-error-cause
*/
class CausedError extends Error {
constructor(message, options) {
super(message, options);
if (
(isObject(options) && 'cause' in options)
&& !('cause' in this)
) {
// .cause was specified but the superconstructor
// did not create an instance property.
const cause = options.cause;
this.cause = cause;
if ('stack' in cause) {
this.stack = this.stack + '\nCAUSE: ' + cause.stack;
}
}
}
}
function isObject(value) {
return value !== null && typeof value === 'object';
} 练习:异常处理
exercises/exception-handling/call_function_test.mjs
测验
请参阅 测验应用程序。