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 "数据非数字"
    }
    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 ,正常的执行流程与错误处理也分开了。