• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-04-13 12:55 Aet 隐藏边栏 |   抢沙发  2 
文章评分 1 次,平均分 5.0

错误处理try...catch

概述

  1. 通常,如果发生错误,脚本就会“死亡”(立即停止),并在控制台将错误打印出来
  2. 但是有一种语法结构 try...catch,它使我们可以“捕获(catch)”错误,因此脚本可以执行更合理的操作,而不是死掉

语法

  1. 首先,执行 try {...} 中的代码
  2. 如果这里没有错误,则忽略 catch (err):执行到 try 的末尾并跳过 catch 继续执行
  3. 如果这里出现错误,则 try 执行停止,控制流转向 catch (err) 的开头。变量 err(我们可以使用任何名称)将包含一个 error 对象,该对象包含了所发生事件的详细信息

示例

  1. 所以,try {...} 块内的 error 不会杀死脚本 —— 我们有机会在 catch 中处理它
  2. 没有 error 的例子:显示 alert (1)(2)

  1. 包含 error 的例子:显示 (1)(3) 行的 alert 中的内容:

注意

  1. try...catch 仅对运行时的 error 有效
    1. 要使得 try...catch 能工作,代码必须是可执行的
    2. 换句话说,它必须是有效的 JavaScript 代码
    3. 如果代码包含语法错误,那么 try..catch 将无法正常工作,例如含有不匹配的花括号:
    4. JavaScript 引擎首先会读取代码,然后运行它。
      在读取阶段发生的错误被称为“解析时间(parse-time)”错误,并且无法恢复(从该代码内部)
      这是因为引擎无法理解该代码
    5. 所以,try...catch 只能处理有效代码中出现的错误
      这类错误被称为“运行时的错误(runtime errors)”,有时被称为“异常(exceptions)”

  1. try...catch 同步执行
    1. 如果在“计划的(scheduled)”代码中发生异常,例如在 setTimeout 中,则 try...catch 不会捕获到异常:
    2. 因为 try...catch 包裹了计划要执行的函数,该函数本身要稍后才执行,这时引擎已经离开了 try...catch 结构

  1. 为了捕获到计划的(scheduled)函数中的异常,那么 try...catch 必须在这个函数内:

Error对象

  1. 发生错误时,JavaScript 会生成一个包含有关此 error 详细信息的对象。然后将该对象作为参数传递给 catch

  1. 对于所有内建的 errorerror 对象具有两个主要属性:
    1. name Error 名称
      例如,对于一个未定义的变量,名称是 "ReferenceError"
    2. message 关于 error 的详细文字描述
    3. 还有其他非标准的属性在大多数环境中可用。其中被最广泛使用和支持的是:
    4. stack 当前的调用栈:
      用于调试目的的一个字符串,其中包含有关导致 error 的嵌套调用序列的信息

可选的 “catch” 绑定

  1. 这是一个最近添加到 JavaScript 的特性
  2. 如果我们不需要 error 的详细信息,catch 也可以忽略它:

使用 “try…catch

  1. JavaScript 支持 JSON.parse(str)方法来解析 JSON 编码的值
  2. 通常,它被用来解析从网络、服务器或是其他来源接收到的数据
    1. 像下面这样调用 JSON.parse

  1. 如果 json 格式错误,JSON.parse 就会生成一个 error,因此脚本就会“死亡”
    1. 我们对此满意吗?当然不!
    2. 如果这样做,当拿到的数据出了问题,那么访问者永远都不会知道原因(除非他们打开开发者控制台)
    3. 代码执行失败却没有提示信息,这真的是很糟糕的用户体验
    4. 在这儿,我们将 catch 块仅仅用于显示信息
      但我们可以做更多的事:发送一个新的网络请求,向访问者建议一个替代方案,将有关错误的信息发送给记录日志的设备

抛出我们自定义的 error

  1. 如果这个 json 在语法上是正确的,但是没有所必须的 name 属性该怎么办?
    1. 这里 JSON.parse 正常执行,但缺少 name 属性对我们来说确实是个 error

  1. 为了统一进行 error 处理,我们将使用 throw 操作符

throw操作符

  1. throw 操作符会生成一个 error 对象
    1. 技术上讲,我们可以将任何东西用作 error 对象
    2. 甚至可以是一个原始类型数据,例如数字或字符串,但最好使用对象,最好使用具有 namemessage 属性的对象(某种程度上保持与内建 error 的兼容性)

  1. JavaScript 中有很多内建的标准 error 的构造器:ErrorSyntaxErrorReferenceErrorTypeError
    1. 我们也可以使用它们来创建 error 对象

  1. 对于内建的 error(不是对于其他任何对象,仅仅是对于 error),name 属性刚好就是构造器的名字。message 则来自于参数(argument

  1. 看看 JSON.parse 会生成什么样的 error
    1. (*) 标记的这一行,throw 操作符生成了包含着我们所给定的 messageSyntaxError,与 JavaScript 自己生成的方式相同
    2. try 的执行立即停止,控制流转向 catch

再次抛出Rethrowing

  1. 上面的例子中,我们使用 try...catch 来处理不正确的数据
  2. 但是在 try {...} 块中是否可能发生 另一个预料之外的 error
    1. 例如编程错误(未定义变量)或其他错误,而不仅仅是这种“不正确的数据”
    2. 在这儿,它捕获到了一个预料之外的 error,但仍然抛出的是同样的 "JSON Error" 信息。这是不正确的,并且也会使代码变得更难以调试

  1. 为了避免此类问题,我们可以采用“重新抛出”技术。规则很简单:
    1. catch 应该只处理它知道的 error,并“抛出”所有其他 error
    2. 更详细地解释为:
    3. Catch 捕获所有 error
    4. catch (err) {...} 块中,我们对 error 对象 err 进行分析
    5. 如果我们不知道如何处理它,那我们就 throw err

try…catch…finally

  1. try...catch 结构可能还有一个代码子句(clause):finally
  2. 如果它存在,它在所有情况下都会被执行:
    1. try 之后,如果没有 error
    2. catch 之后,如果有 error

  1. 示例代码:
    1. 对于 “Make an error?” 的回答是 “Yes”,那么执行 try -> catch -> finally
    2. 如果你的回答是 “No”,那么执行 try -> finally

  1. finally 子句(clause)通常用在:当我们开始做某事的时候,希望无论出现什么情况都要完成某个任务
    1. 代码中的 resultdiff 变量都是在 try...catch 之前 声明的
    2. finally 子句适用于 try...catch 的 任何 出口。这包括显式的 return
    3. 没有 catch 子句的 try...finally 结构也很有用。当我们不想在原地处理 error(让它们掉出去吧),但是需要确保我们启动的处理需要被完成时,我们应当使用它

全局 catch

  1. 这个部分的内容并不是 JavaScript 核心的一部分
  2. 设想一下,在 try...catch 结构外有一个致命的 error,然后脚本死亡了
    1. 有什么办法可以用来应对这种情况吗?我们可能想要记录这个 error,并向用户显示某些内容(通常用户看不到错误信息)等
    2. 规范中没有相关内容,但是代码的执行环境一般会提供这种机制,因为它确实很有用
    3. 例如,Node.JSprocess.on("uncaughtException")
    4. 在浏览器中,我们可以将一个函数赋值给特殊的 window.onerror属性,该函数将在发生未捕获的 error 时执行
  3. 语法
    1. message error 信息
    2. url 发生 error 的脚本的 URL
    3. linecol 发生 error 处的代码的行号和列号
    4. error error 对象

  1. 全局错误处理程序 window.onerror 的作用通常不是恢复脚本的执行 —— 如果发生编程错误,恢复脚本的执行几乎是不可能的,它的作用是将错误信息发送给开发者

自定义 Error,扩展 Error

概述

  1. 当我们在开发某些东西时,经常会需要我们自己的 error 类来反映在我们的任务中可能出错的特定任务
  2. 对于网络操作中的 error,我们需要 HttpError,对于数据库操作中的 error,我们需要 DbError,对于搜索操作中的 error,我们需要 NotFoundError,等等
  3. 我们自定义的 error 应该支持基本的 error 的属性
    1. 例如 messagename,并且最好还有 stack
    2. 但是它们也可能会有其他属于它们自己的属性,例如,HttpError 对象可能会有一个 statusCode 属性,属性值可能为 404403500
  4. JavaScript 允许将 throw 与任何参数一起使用,所以从技术上讲,我们自定义的 error 不需要从 Error 中继承
    1. 但是,如果我们继承,那么就可以使用 obj instanceof Error 来识别 error 对象。因此,最好继承它
    2. 随着开发的应用程序的增长,我们自己的 error 自然会形成形成一个层次结构(hierarchy)。例如,HttpTimeoutError 可能继承自 HttpError,等等

扩展Error

  1. 例如,让我们考虑一个函数 readUser(json),该函数应该读取带有用户数据的 JSON
    1. 在函数内部,我们将使用 JSON.parse。如果它接收到格式不正确的 json,就会抛出 SyntaxError
    2. 但是,即使 json 在语法上是正确的,也不意味着该数据是有效的用户数据,对吧?
    3. 因为它可能丢失了某些必要的数据。例如,对用户来说,必不可少的是 nameage 属性

  1. 我们的函数 readUser(json) 不仅会读取 JSON,还会检查(“验证”)数据。如果没有所必须的字段,或者(字段的)格式错误,那么就会出现一个 error
    1. 并且这些并不是 SyntaxError,因为这些数据在语法上是正确的,这些是另一种错误
    2. 我们称之为 ValidationError,并为之创建一个类
    3. 这种类型的错误也应该包含有关违规字段的信息
    4. 我们的 ValidationError 类应该继承自 Error
    5. Error 类是内建的,但我们可以通过下面这段近似代码理解我们要扩展的内容:

  1. 从其中继承 ValidationError 试一试:
    1. (1) 行中我们调用了父类的 constructorJavaScript 要求我们在子类的 constructor 中调用 super,所以这是必须的。父类的 constructor 设置了 message 属性
    2. 父类的 constructor 还将 name 属性的值设置为了 "Error",所以在 (2) 行中,我们将其重置为了右边的值

  1. 也可以看看 err.name,像这样:
    1. 使用 instanceof 的版本要好得多,因为将来我们会对 ValidationError 进行扩展,创建它的子类型
    2. 例如 PropertyRequiredError。而 instanceof 检查对于新的继承类也适用。所以这是面向未来的做法

深入继承

  1. ValidationError 类是非常通用的。很多东西都可能出错
    1. 对象的属性可能缺失或者属性可能有格式错误(例如 age 属性的值为一个字符串而不是数字)
    2. 我们针对缺少属性的错误来制作一个更具体的 PropertyRequiredError
    3. 这个新的类 PropertyRequiredError 使用起来很简单:我们只需要传递属性名:new PropertyRequiredError(property)。人类可读的 message 是由 constructor 生成的

MyError

  1. 注意,在 PropertyRequiredError constructor 中的 this.name 是通过手动重新赋值的
    1. 这可能会变得有些乏味 —— 在每个自定义 error 类中都要进行 this.name = <class name> 赋值操作
    2. 我们可以通过创建自己的“基础错误(basic error)”类来避免这种情况,该类进行了 this.name = this.constructor.name 赋值
    3. 然后让所有我们自定义的 error 都从这个“基础错误”类进行继承

包装异常

  1. 上面代码中的函数 readUser 的目的就是“读取用户数据”。在这个过程中可能会出现不同类型的 error
  2. 目前我们有了 SyntaxErrorValidationError,但是将来,函数 readUser 可能会不断壮大,并可能会产生其他类型的 error
  3. 调用 readUser 的代码应该处理这些 error。现在它在 catch 块中使用了多个 if 语句来检查 error 类,处理已知的 error,并再次抛出未知的 error
    1. 如果 readUser 函数会产生多种 error,那么我们应该问问自己:我们是否真的想每次都一一检查所有的 error 类型?
    2. 通常答案是 “No”:我们希望能够“比它高一个级别”。我们只想知道这里是否是“数据读取异常” —— 为什么发生了这样的 error 通常是无关紧要的
    3. 或者,如果我们有一种方式能够获取 error 的详细信息那就更好了,但前提是我们需要
    4. 我们所描述的这项技术被称为“包装异常”

  1. 包装异常
    1. 我们将创建一个新的类 ReadError 来表示一般的“数据读取” error
    2. 函数readUser 将捕获内部发生的数据读取 error,例如 ValidationErrorSyntaxError,并生成一个 ReadError 来进行替代
    3. 对象 ReadError 会把对原始 error 的引用保存在其 cause 属性中
    4. 之后,调用 readUser 的代码只需要检查 ReadError,而不必检查每种数据读取 error。并且,如果需要更多 error 细节,那么可以检查 readUsercause 属性

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享