1. JavaScript 基础

  2. 函数进阶

  3. 深入数据和类型

  4. 运算符

  5. 浏览器存储

  6. Document

  7. Web API

  8. 事件

  9. 错误处理

  10. 异步编程

  11. 网络请求

  12. 模块

  13. 练习

  14. 实例

  15. 工具与规范

try…catch

当我们的程序遇到异常时,错误会在控制台被打印出来,同时程序会停止执行。

alert(word) // word is not defined

alert("程序结束") // 不会被执行

有的时候我们希望能处理这个错误,并且不希望程序停止, try...catch 就可以达到这个作用。

try...catch 语法如下:

try {
    // 代码
} catch {
    // 错误捕获
}
  1. 首先会执行 try { ... } 中的语句。
  2. 如果没有遇到异常 ,则会继续往下执行,跳过 catch
  3. 在遇到异常时,会忽略接下来的代码,执行 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(json)
    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 new Error("数据非数字")
    }
    return true
}

function testInt(n) {
    // 检测是否是整数
    if (!Number.isInteger(n)) {
        return new Error("数据非整数")
    }
    return true
}

function testRange(n) {
    // 检测是否有效月份
    if (n < 1 || 12 < n) {
        return new Error("非有效月份")
    }
    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.message)
        }
    } else {
        alert("解析出错,错误是:" + result.message)
    }
} else {
    alert("解析出错,错误是:" + result.message)
}

代码中进行了是否为数字、是否是整数、是否为有效月份的检测,检测通过返回 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 ,正常的执行流程与错误处理也分开了。