当前位置: 首页 > news >正文

JavaScript--错误处理

一,错误处理,"try...catch"

1.1 语法

try { // 代码... } catch (err) { // 错误捕获 }

它按照以下步骤执行:

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

try...catch同步执行

如果在“计划的(scheduled)”代码中发生异常,例如在setTimeout中,则try...catch不会捕获到异常:

try { setTimeout(function() { noSuchVariable; // 脚本将在这里停止运行 }, 1000); } catch (err) { alert( "不工作" ); }

因为try...catch包裹了计划要执行的函数,该函数本身要稍后才执行,这时引擎已经离开了try...catch结构。

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

setTimeout(function() { try { noSuchVariable; // try...catch 处理 error 了! } catch { alert( "error 被在这里捕获了!" ); } }, 1000);

1.2error对象

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

对于所有内建的 error,error 对象具有两个主要属性:

name

Error 名称。例如,对于一个未定义的变量,名称是"ReferenceError"

message

关于 error 的详细文字描述。

还有其他非标准的属性在大多数环境中可用。其中被最广泛使用和支持的是:

stack

当前的调用栈:用于调试目的的一个字符串,其中包含有关导致 error 的嵌套调用序列的信息。

try { lalala; // error, variable is not defined! } catch (err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...call stack) // 也可以将一个 error 作为整体显示出来 // error 信息被转换为像 "name: message" 这样的字符串 alert(err); // ReferenceError: lalala is not defined }

1.3 使用

让我们一起探究一下真实场景中try...catch的用例。

正如我们所知道的,JavaScript 支持JSON.parse(str)方法来解析 JSON 编码的值。

通常,它被用来解析从网络、服务器或是其他来源接收到的数据。

如果json格式错误,JSON.parse就会生成一个 error,因此脚本就会“死亡”。

我们对此满意吗?当然不!

如果这样做,当拿到的数据出了问题,那么访问者永远都不会知道原因(除非他们打开开发者控制台)。代码执行失败却没有提示信息,这真的是很糟糕的用户体验。

让我们用try...catch来处理这个 error:

letjson ="{ bad json }";try{letuser =JSON.parse(json);// <-- 当出现 error 时...alert( user.name);// 不工作}catch(err) {// ...执行会跳转到这里并继续执行alert("很抱歉,数据有错误,我们会尝试再请求一次。");alert( err.name);alert( err.message); }

1.4throw操作符

如果这个json在语法上是正确的,但是没有所必须的name属性该怎么办?

letjson ='{ "age": 30 }';// 不完整的数据try{letuser =JSON.parse(json);// <-- 没有 erroralert( user.name);// 没有 name!}catch(err) {alert("doesn't execute"); }

这里JSON.parse正常执行,但缺少name属性对我们来说确实是个 error。

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

throw的语法

throw<errorobject>

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

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

leterror =newError(message);// 或leterror =newSyntaxError(message);leterror =newReferenceError(message);// ...

让我们来看看JSON.parse会生成什么样的 error:

try{JSON.parse("{ bad json o_O }"); }catch(err) {alert(err.name);// SyntaxErroralert(err.message);// Unexpected token b in JSON at position 2}

正如我们所看到的, 那是一个SyntaxError

在我们的示例中,缺少name属性就是一个 error,因为用户必须有一个name

所以,让我们抛出这个 error。

letjson ='{ "age": 30 }';// 不完整的数据try{letuser =JSON.parse(json);// <-- 没有 errorif(!user.name) {thrownewSyntaxError("数据不全:没有 name");// (*)}alert( user.name); }catch(err) {alert("JSON Error: "+ err.message);// JSON Error: 数据不全:没有 name}

1.5 再次抛出

catch 会捕获到所有来自于try的 error。在这儿,它捕获到了一个预料之外的 error,但仍然抛出的是同样的错误 信息。这是不正确的,并且也会使代码变得更难以调试。

catch应该只处理它知道的 error,并“抛出”所有其他 error。

“再次抛出(rethrowing)”技术可以被更详细地解释为:

  1. Catch 捕获所有 error。
  2. catch (err) {...}块中,我们对 error 对象err进行分析。
  3. 如果我们不知道如何处理它,那我们就throw err

通常,我们可以使用instanceof操作符判断错误类型:

try{ user = {/*...*/}; }catch(err) {if(errinstanceofReferenceError) {alert('ReferenceError');// 访问一个未定义(undefined)的变量产生了 "ReferenceError"} }
letjson ='{ "age": 30 }';// 不完整的数据try{letuser =JSON.parse(json);if(!user.name) {thrownewSyntaxError("数据不全:没有 name"); }blabla();// 预料之外的 erroralert( user.name); }catch(err) {if(errinstanceofSyntaxError) {alert("JSON Error: "+ err.message); }else{throwerr;// 再次抛出 (*)} }
  1. 调用readData(

  2. 触发内部try块中的错误

    执行到blabla();时,因为blabla这个函数在全局作用域中根本不存在,JavaScript 引擎会立刻抛出ReferenceError(引用错误)try块内的代码执行就此中断,直接跳转到内部的catch块。
  3. 进入内部catch (err)块进行错误筛查

    此时捕获到的errReferenceError。代码执行判断:if (!(err instanceof SyntaxError))
  4. 内部错误“重新抛出”(Rethrow)

    • 因为判断条件为真,if内部的throw err;被执行。内部的catch觉得自己没法处理这个ReferenceError(它只认识SyntaxError),于是把这个错误原封不动地向上抛,readData()函数执行被强制终止,并把这个错误传到了外面。

  5. 外部的try...catch接盘

    代码回到全局作用域,外层的try { readData(); }检测到了从内部抛出的ReferenceError。外层的catch (err)成功捕获到这个错误。
  6. 最终执行结果

  7. 执行外层的alert( "External catch got: " + err );。浏览器弹出警告框,显示捕获到的错误信息。

catch只应该处理你明确知道怎么处理的错误。如果遇到你无法识别或无法解决的错误,绝对不要对它保持沉默(不要写空的catch块什么都不做),而应该使用throw err把它抛出去,让调用该函数的外部代码或者全局错误监听器来处理。这能防止程序“静默失败”,避免留下难以排查的 Bug。

1.6 try.....catch........finally

try { ... 尝试执行的代码 ... } catch (err) { ... 处理 error ... } finally { ... 总是会执行的代码 ... }
let num = +prompt("输入一个正整数?", 35) let diff, result; function fib(n) { if (n < 0 || Math.trunc(n) != n) { throw new Error("不能是负数,并且必须是整数。"); } return n <= 1 ? n : fib(n - 1) + fib(n - 2); } let start = Date.now(); try { result = fib(num); } catch (err) { result = 0; } finally { diff = Date.now() - start; } alert(result || "出现了 error"); alert( `执行花费了 ${diff}ms` );

finally子句适用于try...catch任何出口。这包括显式的return

二。自定义 Error,扩展 Error

2.1拓展Error

Error类是内建的,但我们可以通过下面这段近似代码理解我们要扩展的内容:

// JavaScript 自身定义的内建的 Error 类的“伪代码” class Error { constructor(message) { this.message = message; this.name = "Error"; // (不同的内建 error 类有不同的名字) this.stack = <call stack>; // 非标准的,但大多数环境都支持它 } }

现在让我们从其中继承ValidationError试一试:

class ValidationError extends Error { constructor(message) { super(message); // (1) this.name = "ValidationError"; // (2) } } function test() { throw new ValidationError("Whoops!"); } try { test(); } catch(err) { alert(err.message); // Whoops! alert(err.name); // ValidationError alert(err.stack); // 一个嵌套调用的列表,每个调用都有对应的行号 }
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } // 用法 function readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new ValidationError("No field: age"); } if (!user.name) { throw new ValidationError("No field: name"); } return user; } // try..catch 的工作示例 try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { alert("Invalid data: " + err.message); // Invalid data: No field: name } else if (err instanceof SyntaxError) { // (*) alert("JSON Syntax Error: " + err.message); } else { throw err; // 未知的 error,再次抛出 (**) } }

我们也可以看看err.name,像这样:

// ... // instead of (err instanceof SyntaxError) } else if (err.name == "SyntaxError") { // (*) // ...

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

深入继承

ValidationError类是非常通用的。很多东西都可能出错。对象的属性可能缺失或者属性可能有格式错误(例如age属性的值为一个字符串而不是数字)。

class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } } class PropertyRequiredError extends ValidationError { constructor(property) { super("No property: " + property); this.name = "PropertyRequiredError"; this.property = property; } } // 用法 function readUser(json) { let user = JSON.parse(json); if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } return user; } // try..catch 的工作示例 try { let user = readUser('{ "age": 25 }'); } catch (err) { if (err instanceof ValidationError) { alert("Invalid data: " + err.message); // Invalid data: No property: name alert(err.name); // PropertyRequiredError alert(err.property); // name } else if (err instanceof SyntaxError) { alert("JSON Syntax Error: " + err.message); } else { throw err; // 未知 error,将其再次抛出 } }

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

让我们称之为MyError

class MyError extends Error { constructor(message) { super(message); this.name = this.constructor.name; } } class ValidationError extends MyError { } class PropertyRequiredError extends ValidationError { constructor(property) { super("No property: " + property); this.property = property; } } // name 是对的 alert( new PropertyRequiredError("field").name ); // PropertyRequiredError

2.2包装异常

将来,函数readUser可能会不断壮大,并可能会产生其他类型的 error。

我们是否真的想每次都一一检查所有的 error 类型?

通常答案是 “No”:我们希望能够“比它高一个级别”。我们只想知道这里是否是“数据读取异常” —— 为什么发生了这样的 error 通常是无关紧要的

我们所描述的这项技术被称为“包装异常”。

  1. 我们将创建一个新的类ReadError来表示一般的“数据读取” error。
  2. 函数readUser将捕获内部发生的数据读取 error,例如ValidationErrorSyntaxError,并生成一个ReadError来进行替代。
  3. 对象ReadError会把对原始 error 的引用保存在其cause属性中。
class ReadError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'ReadError'; } } class ValidationError extends Error { /*...*/ } class PropertyRequiredError extends ValidationError { /* ... */ } function validateUser(user) { if (!user.age) { throw new PropertyRequiredError("age"); } if (!user.name) { throw new PropertyRequiredError("name"); } } function readUser(json) { let user; try { user = JSON.parse(json); } catch (err) { if (err instanceof SyntaxError) { throw new ReadError("Syntax Error", err); } else { throw err; } } try { validateUser(user); } catch (err) { if (err instanceof ValidationError) { throw new ReadError("Validation Error", err); } else { throw err; } } } try { readUser('{bad json}'); } catch (e) { if (e instanceof ReadError) { alert(e); // Original error: SyntaxError: Unexpected token b in JSON at position 1 alert("Original error: " + e.cause); } else { throw e; } }
http://www.gsyq.cn/news/1605187.html

相关文章:

  • AI Agent开发实战:从零构建一个能自主规划任务的智能体
  • AI 学习助手:基于 HarmonyOS ArkTS 的智能学习伴侣开发实践
  • 使用visual studio和ai制作ppt
  • 关于多线程归并排序的性能瓶颈与优化方案的技术7
  • HFSS求解设置实战解析:从驱动求解到本征模求解的核心配置
  • GoChatIAI -Go语言AI应用服务平台(1)
  • IntelliJ IDEA 之工程模块管理
  • 全球高端健身房都在用什么跑步机?解析Precor必确的核心技术与产品优势
  • 电路板质量出问题,怎么查源头?全流程追溯体系给出答案
  • 服务网格——让微服务“自动驾驶“的黑科技
  • Playwright 自动化操控 X(Twitter) 发帖踩坑实录
  • 2026年适配维普降AI率软件横评:亲测8款工具,把AI率稳控在安全线内
  • SolidWorks_曲线与曲面设计19_曲面与实体混合建模
  • 3分钟快速上手:HS2-HF Patch终极安装与配置指南
  • ClickHouse:极速OLAP引擎解析
  • 2025轻松指南:零基础医疗会议转待办,包教包会避坑干货满满
  • 搞skill比搞向量库简单多了,要干的事全搞成skill就好了
  • 终极免费卡拉OK游戏指南:快速上手UltraStar Deluxe的完整教程 [特殊字符]
  • 分治策略在图像处理算法中的应用与优化的技术7
  • 变更管理化技术中的变更请求变更控制变更实施
  • 关于基于优先搜索的路径规划算法性能分析的技术7
  • 密码学h面试大法---h(自用版)更新中~(^v^)
  • (InputStream的源码、FilterInputStream源码、BufferedInputStream的源码解读前言)AtomicReferenceFieldUpdater.class和Sys
  • ingress-nginx
  • FanControl终极指南:如何在Windows上实现智能风扇控制,告别噪音烦恼
  • 在线教程丨32K上下文一次解析数十页文档,百度开源Unlimited OCR,重构长文档复杂场景
  • LPDDR5 ZQ校准实战:从背景校准到命令模式的深度解析
  • 从DCB到OSB:北斗多频多系统硬件延迟改正的演进与实践
  • 更新int count变量,fill()函数中getInIfOpen().read(buffer, pos, buffer.length - pos)这行代码的返回值为8192,
  • D3KeyHelper终极指南:暗黑3智能游戏自动化与按键管理解决方案