try...catch
Baseline
Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since 2015年7月.
try...catch 语句由一个 try 块和一个 catch 块或 finally 块(或两者皆有)组成。首先执行 try 块中的代码,如果它抛出异常,则将执行 catch 块中的代码。finally 块中的代码将在控制流退出整个结构之前始终被执行。
尝试一下
try {
nonExistentFunction();
} catch (error) {
console.error(error);
// Expected output: ReferenceError: nonExistentFunction is not defined
// (注意:具体输出结果可能因浏览器而异)
}
语法
try {
tryStatements
} catch (exceptionVar) {
catchStatements
} finally {
finallyStatements
}
tryStatements-
要执行的语句。
catchStatements-
try块抛出异常后执行的语句。 exceptionVar可选-
可选的标识符或模式,用于保存关联的
catch块所捕获到的异常。如果catch块不使用异常的值,你可以省略exceptionVar及其周围的括号。 finallyStatements-
在控制流退出
try...catch...finally结构之前执行的语句。这些语句无论是否抛出或捕获异常都会执行。
描述
try 语句总是以 try 块开始。并且,至少存在 catch 块或 finally 块。也可以同时存在 catch 和 finally 块。这为我们提供了 try 语句的三种形式:
try...catchtry...finallytry...catch...finally
与其他结构(如 if 或 for)不同,try、catch 和 finally 块必须是块,而不是单个语句。
try doSomething(); // SyntaxError
catch (e) console.log(e);
catch 块包含指定在 try 块中抛出异常时要执行的语句。如果 try 块(或在 try 块内部调用的函数)中的任何语句抛出异常,则立即转移到 catch 块。如果 try 块中没有抛出异常,则跳过 catch 块。
finally 块总是在控制流退出 try...catch...finally 结构之前执行。它总是执行,无论是否抛出或捕获异常。
你可以嵌套多个 try 语句。如果内部 try 语句没有 catch 块,则使用包裹它的 try 语句的 catch 块。
你可以使用 try 语句来处理 JavaScript 异常。有关 JavaScript 异常的更多信息,请参阅 JavaScript 指南。
catch 绑定
当 try 块中抛出异常时,exceptionVar(即 catch (e) 中的 e)保存了异常的值。你可以使用这个绑定获取有关抛出的异常的信息。这个绑定只能在 catch 块的作用域中使用。
它不需要是单个标识符。你可以使用解构模式来一次性为多个标识符赋值。
try {
throw new TypeError("哦豁");
} catch ({ name, message }) {
console.log(name); // "TypeError"
console.log(message); // "哦豁"
}
catch 子句创建的绑定与 catch 块处于同一作用域内,因此 catch 块中声明的变量不能与 catch 子句创建的绑定具有相同的名称。(有一个例外,但它是已弃用的语法。)
try {
throw new TypeError("哦豁");
} catch ({ name, message }) {
var name; // SyntaxError: Identifier 'name' has already been declared
let message; // SyntaxError: Identifier 'message' has already been declared
}
异常绑定是可写的。例如,你可能需要规范异常值,以确保它是一个 Error 对象。
try {
throw "哦豁;这不是一个 Error 对象";
} catch (e) {
if (!(e instanceof Error)) {
e = new Error(e);
}
console.error(e.message);
}
如果你不需要异常值,你可以省略异常变量及其周围的括号。
function isValidJSON(text) {
try {
JSON.parse(text);
return true;
} catch {
return false;
}
}
finally 块
finally 块包含要在 try 和 catch 块之后,但在 try...catch...finally 块之后的语句之前执行的语句。控制流将始终进入 finally 块,其执行可以按以下方式进行:
- 在
try...finally结构中,控制流离开try块后立即执行(无论是在最后一条语句之后,还是在throw、return、break或continue语句之后); - 在
try...catch...finally结构中,在控制流离开catch块后立即执行; - 在
try...catch...finally结构中,在控制流离开try块后立即执行,除非通过throw语句退出(在这种情况下,控制流会先进入catch块)。
如果进入了 finally 块的控制流来自 try 或 catch 块中的控制流语句(return、throw、break、continue),则该语句的效果将被延迟,直到 finally 块中执行的最后一条语句之后。例如,如果在 try 块抛出异常,即使没有 catch 块来处理异常,finally 块仍然会执行,并且异常会在 finally 块执行完毕后立即抛出。
但是,这条规则有个例外:如果 finally 块中执行的最后一条语句本身就是一个控制流语句,那么该语句将覆盖之前的控制流语句的效果(没有延迟);有关示例,请参阅从 finally 块返回。在 finally 块中使用控制流语句(return、throw、break、continue)通常不是一个好主意,因为它们可以覆盖之前执行的控制流语句的效果,而这很少是预期行为。大多数情况下,finally 块应该用于执行不修改主要逻辑的清理代码。
示例
>无条件捕获块
当使用 catch 块时,catch 块将在 try 块中抛出异常时被执行。例如,在下面的代码中,控制流将被转移到 catch 块。
try {
throw "我的异常"; // 产生异常
} catch (e) {
// 处理任何异常的语句
logMyErrors(e); // 将异常对象传递给错误处理器
}
catch 块指定了一个标识符(如上例中的 e),它保存了异常的值。这个值只能在 catch 块的作用域内使用。
条件捕获块
你可以通过将 try...catch 块与 if...else if...else 结构组合起来,创建“条件 catch 块”。例如:
try {
myroutine(); // 可能会抛出三种类型的异常
} catch (e) {
if (e instanceof TypeError) {
// 处理 TypeError 异常的语句
} else if (e instanceof RangeError) {
// 处理 RangeError 异常的语句
} else if (e instanceof EvalError) {
// 处理 EvalError 异常的语句
} else {
// 处理未指定异常的语句
logMyErrors(e); // 将异常对象传递给错误处理器
}
}
一个常见的用例是仅捕获(并消除)一小部分预期错误,然后在其他情况下重新抛出错误:
try {
myRoutine();
} catch (e) {
if (e instanceof RangeError) {
// 处理这个非常常见的预期错误的语句
} else {
throw e; // 重新抛出错误,没有任何改变
}
}
这类似与其他语言中的语法,比如 Java:
try {
myRoutine();
} catch (RangeError e) {
// 处理这个非常常见的预期错误的语句
}
// 其他错误被隐式重新抛出
嵌套 try 块
首先,让我们看看下面的代码会发生什么:
try {
try {
throw new Error("哦豁");
} finally {
console.log("finally");
}
} catch (ex) {
console.error("外层", ex.message);
}
// 输出:
// "finally"
// "外层" "哦豁"
现在,如果我们已经在内部的 try 块中通过添加 catch 块捕获了异常:
try {
try {
throw new Error("哦豁");
} catch (ex) {
console.error("内层", ex.message);
} finally {
console.log("最终");
}
} catch (ex) {
console.error("外层", ex.message);
}
// 输出:
// "内层" "哦豁"
// "最终"
现在,让我们重新抛出错误。
try {
try {
throw new Error("哦豁");
} catch (ex) {
console.error("内层", ex.message);
throw ex;
} finally {
console.log("最终");
}
} catch (ex) {
console.error("外层", ex.message);
}
// 输出:
// "内层" "哦豁"
// "最终"
// "外层" "哦豁"
任何特定的异常只会被直接包裹它的 catch 块捕获一次,除非该异常被重新抛出。当然,如果在“内部”代码块中触发了任何新的异常(因为 catch 块中的代码可能会执行某些操作并抛出异常),这些异常将由外部的 catch 块捕获。
使用 finally 进行资源清理
以下示例展示了 finally 块的一个用例。代码打开一个文件,然后执行使用该文件的语句;finally 块确保文件在使用后始终关闭,即使抛出了异常。
openMyFile();
try {
// 占用资源
writeMyFile(theData);
} finally {
closeMyFile(); // 始终关闭资源
// 任何未捕获的异常都会在这里被延迟处理
}
同样,try 块中任何 return 语句的效果都会在 finally 块结束后才生效,尽管返回值表达式是在进入 finally 块之前计算的。
function safeWriteMyFile() {
openMyFile();
try {
return writeMyFile(theData); // 函数调用被求值
} finally {
closeMyFile(); // 始终关闭资源
// return 在这里被延迟
}
}
从 finally 块返回
以下示例说明了 finally 块中的控制流语句的行为。当控制流通过第一个 return 语句退出 try 块时,返回值表达式(order.sort())在进入 finally 块之前被计算,函数计划在 finally 块执行完毕后返回该值。然而,finally 块中的 return 语句会覆盖前一个 return 语句的效果,包括其返回值。
function doIt() {
const order = ["z"];
try {
order.push("try");
return order.sort(); // “z”现在在“try”之后
} finally {
order.push("finally");
return order;
}
}
doIt();
// 返回 ["try", "z", "finally"],而不是 ["finally", "try", "z"] 或 ["try", "z"]
同样的逻辑也适用于其他控制流语句。在这里,函数首先计划抛出值 "catch",但最终返回值 "finally"。
function doIt() {
try {
throw "catch";
} finally {
return "finally";
}
}
doIt(); // 返回“finally”
再次强调,不建议在 finally 块中使用控制流语句,因为这可能并非预期效果。
规范
| Specification |
|---|
| ECMAScript® 2027 Language Specification> # sec-try-statement> |