JavaScript 基础
Document
运算符
深入数据和类型
函数进阶
原型、继承
类
浏览器存储
Web API
事件
错误处理
异步编程
网络请求
模块
练习
实例
工具与规范
软件架构模式
设计模式
try…catch
当我们的程序遇到异常时,错误会在控制台被打印出来,同时程序会停止执行。
alert(word) // word is not defined
alert("程序结束") // 不会被执行
有的时候我们希望能处理这个错误,并且不希望程序停止, try...catch
就可以达到这个作用。
try...catch
语法如下:
try {
// 代码
} catch {
// 错误捕获
}
- 首先会执行
try { ... }
中的语句。 - 如果没有遇到异常 ,则会继续往下执行,跳过
catch
。 - 在遇到异常时,会忽略接下来的代码,执行
catch
中的代码。
$$jsdemo$$
$$edit$$
try {
alert("开始执行")
alert(word) // 跳到 catch 内
alert("结束执行") // 不会被执行到
} catch {
alert("捕获到错误")
}
alert("程序结束")
从例子中我们可以看出,异常的发生虽然中断了 try { ... }
中的执行,但并未影响程序整体的执行,并且我们的错误会被 catch
捕获到。
Error 对象
捕获到异常时,会有一个包含异常相关信息的 Error 对象被作为参数传递给 catch
。
$$jsdemo$$
$$edit$$
try {
alert("开始执行")
alert(word) // 跳到 catch 内
alert("结束执行") // 不会被执行到
} catch (error) {
alert(error instanceof Error) // true
}
Error 对象有三个重要属性:
- name: Error 类型的名称。
- message: Error 的详细描述。
- stack:调用相关的信息。
$$jsdemo$$
$$edit$$
try {
alert("开始执行")
alert(word) // 跳到 catch 内
alert("结束执行") // 不会被执行到
} catch (error) {
alert(error.name) // ReferenceError
alert(error.message) // word is not defined
alert(error.stack) // ReferenceError: word is not defined at ...
}
$$tip
很多时候的错误是可以避免的,比如变量未定义的错误可以在开发阶段就排除。但一些错误是运行中发生的,而 try ... catch
正是用来处理这种场景。
$$
$$jsdemo$$
$$edit$$
// data 是服务器返回的数据
// 预期的数据
// let data = "{ name: '小明' }"
// 实际得到的数据
let data = "{ xxx }"
try {
let user = JSON.parse(data)
alert(user.name)
} catch {
alert("服务器数据出错")
}
alert("程序结束")
finally
try ... catch
后可以跟一个可选的 finally { ... }
, finally { ... }
中的语句不管有没有错误都在最终会被执行。
$$jsdemo$$
$$edit$$
try {
alert("try ...")
} catch {
alert("catch ...")
} finally {
alert("finally ...")
}
自定义异常
throw
操作符可以抛出一个异常对象, JavaScript 中有很多内建的错误类型,比如 Error
(基本错误)、 SyntaxError
(语法错误)、 TypeError
(类型错误)等。
$$jsdemo$$
$$edit$$
try {
throw Error("自定义错误")
} catch (error) {
alert(error) // Error: 自定义错误
}
使用 try … catch 的好处
使用 try ... catch
最大的好处是我们可以统一地处理不正确的情况分支,保持我们代码的优雅。
在不用 try .. catch
的情况下, 我们有以下的代码:
$$jsdemo$$
$$edit$$
function testNumber(n) {
// 检测是否为数字
if (isNaN(n)) {
// 非数字
return "数据非数字"
}
return true
}
function testInt(n) {
// 检测是否是整数
if (!Number.isInteger(n)) {
return "数据非整数"
}
return true
}
function testRange(n) {
// 检测是否有效月份
if (n < 1 || 12 < n) {
return "非有效月份"
}
return true
}
// 用户输入的月份
let month = prompt("请输入月份:")
// 检测是否为数字
let result = testNumber(month)
if (result === true) {
// 转成数字
month = Number(month)
// 检测是否是整数
result = testInt(month)
if (result === true) {
// 检测是否有效月份
result = testRange(month)
if (result === true) {
alert("你输入的月份是:" + month)
} else {
alert("解析出错,错误是:" + result)
}
} else {
alert("解析出错,错误是:" + result)
}
} else {
alert("解析出错,错误是:" + result)
}
代码中进行了是否为数字、是否是整数、是否为有效月份的检测,检测通过返回 true
,检测失败则返回错误信息。
在不使用 try ... catch
的有以下坏处:
- 代码冗长
- 正常数据与错误数据混合返回
- 每次都得
if
判断下返回的数据 - 重复的错误处理语句
以下代码是使用 try ... catch
改写的结果:
$$jsdemo$$
$$edit$$
function testNumber(n) {
// 检测是否为数字
if (isNaN(n)) {
// 非数字
throw new Error("数据非数字")
}
}
function testInt(n) {
// 检测是否是整数
if (!Number.isInteger(n)) {
throw new Error("数据非整数")
}
}
function testRange(n) {
// 检测是否有效月份
if (n < 1 || 12 < n) {
throw new Error("非有效月份")
}
}
try {
// 用户输入的月份
let month = prompt("请输入月份:")
// 检测是否为数字
testNumber(month)
// 转成数字
month = Number(month)
testInt(month)
testRange(month)
alert("你输入的月份是:" + month)
} catch (error) {
alert("解析出错,错误是:" + error.message)
}
错误的处理统一放到了 catch { ... }
当中,与正常的执行流程分开,并且没有了冗余的的 if
嵌套。
如果错误发生在深层嵌套的函数里面,不使用 try ... catch
的话,那么错误信息必须一层一层地通过 return
返回,获取函数数据时还必须先判断是否为 Error
。
$$jsdemo$$
$$edit$$
function getInput() {
let result = prompt("请输入用户名:")
if (result.length < 2) {
return new Error("用户名必须大于等于 2 个字符")
}
return result
}
function getUsername() {
let result = getInput()
// 每次都必须判断调用的函数返回的值是否是错误
if (result instanceof Error) {
// 如果是错误直接返回
return result
}
if (result == "管理员") {
return new Error("用户名不能为管理员")
}
return result
}
let result = getUsername()
// 每次都必须判断调用的函数返回的值是否是错误
if (result instanceof Error) {
alert("输入不合法:" + result.message)
} else {
alert("你的用户名是:" + result)
}
同样使用 try ... catch
改写以上的代码:
$$jsdemo$$
$$edit$$
function getInput() {
let result = prompt("请输入用户名:")
if (result.length < 2) {
throw new Error("用户名必须大于等于 2 个字符")
}
return result
}
function getUsername() {
let result = getInput()
if (result == "管理员") {
throw new Error("用户名不能为管理员")
}
return result
}
try {
let result = getUsername()
// 无需再判断返回的数据是否为 Error
alert("你的用户名是:" + result)
} catch (error) {
alert("输入不合法:" + error.message)
}
因为正常的数据与错误不再混合返回,因此无需再判断返回值是否为 Error
,正常的执行流程与错误处理也分开了。