throw
try
语句try
代码块catch
子句finally
子句Error
及其子类Error
Error
的内置子类Error
error.cause
链接错误 [ES2022].cause
的替代方案:自定义错误类本章介绍 JavaScript 如何处理异常。
为什么 JavaScript 不更频繁地抛出异常?
JavaScript 直到 ES3 才支持异常。这解释了为什么该语言及其标准库很少使用它们。
考虑以下代码。它将存储在文件中的配置文件读取到一个包含 Profile
类实例的数组中
function readProfiles(filePaths) {
const profiles = [];
for (const filePath of filePaths) {
try {
const profile = readOneProfile(filePath);
.push(profile);
profilescatch (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');
.filePath = filePath;
errthrow err;
使用 Error
的子类 之一。
子类化 Error
(稍后 将解释更多详细信息)
class MyError extends Error {
}function func() {
throw new MyError('Problem!');
}.throws(
assert=> func(),
() ; MyError)
try
语句try
语句的最大版本如下所示
try {
«try_statements»catch (error) {
}
«catch_statements»finally {
}
«finally_statements» }
我们可以组合这些子句,如下所示
try-catch
try-finally
try-catch-finally
try
代码块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)
} .equal(err, errorObject);
assert }
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 {
} .destroy();
resource }
finally
始终执行finally
子句始终执行,即使抛出错误(A 行)
let finallyWasExecuted = false;
.throws(
assert=> {
() try {
throw new Error(); // (A)
finally {
} = true;
finallyWasExecuted
},
}Error
;
).equal(finallyWasExecuted, true); assert
即使有 return
语句(A 行)
let finallyWasExecuted = false;
function func() {
try {
return; // (A)
finally {
} = true;
finallyWasExecuted
}
}func();
.equal(finallyWasExecuted, true); assert
Error
及其子类Error
是所有内置错误类的公共超类。
Error
这就是 Error
的实例属性和构造函数的样子
class Error {
// Instance properties
: string;
message?: any; // ES2022
cause: string; // non-standard but widely supported
stack
constructor(
: string = '',
message?: ErrorOptions // ES2022
options;
)
}interface ErrorOptions {
?: any; // ES2022
cause }
构造函数有两个参数
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!');
.equal(String(err), 'Error: Hello!');
assert.equal(err.message, 'Hello!'); assert
如果我们省略消息,则使用空字符串作为默认值(继承自 Error.prototype.message
)
如果我们省略消息,则它是空字符串
.equal(new Error().message, ''); assert
.stack
实例属性 .stack
不是 ECMAScript 功能,但它得到 JavaScript 引擎的广泛支持。它通常是一个字符串,但其确切结构没有标准化,并且在不同的引擎之间有所不同。
这就是它在 JavaScript 引擎 V8 上的样子
const err = new Error('Hello!');
.equal(
assert.stack,
err`
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'});
.equal(err.cause, 'the cause'); assert
有关如何使用此属性的信息,请参阅 §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
测验
请参阅 测验应用程序。