当前位置: 首页 > 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.jsqmd.com/news/1093229/

相关文章:

  • OpenClaw(龙虾)2026 最新安装部署终极指南
  • xref_data_to_array
  • CSDN博客-第1天-单神经元反向传播
  • 计算机二级基础知识-计算机体系结构
  • 中小微企业建站首选!PageAdmin CMS,零代码搞定官网运维
  • chunk重叠overlap设多少:切断上下文的坑
  • 支持多端生成的AI开发软件怎么选?功能对比指南
  • AI编程新范式:Skills技能库如何提升Claude、Cursor代码生成质量
  • AI Agent开发实战:从零构建一个能自主规划任务的智能体
  • Python学习笔记·第24天:Pandas数据清洗——缺失值、重复值与透视表实战
  • 使用visual studio和ai制作ppt
  • AI 学习助手:基于 HarmonyOS ArkTS 的智能学习伴侣开发实践
  • 第一批被龙虾气到的人出现了
  • Vue3 项目从开发到上线:环境变量、打包优化与 Nginx 部署全流程
  • 相处的艺术:尊重与边界
  • 企业知识图谱的拐点: 当本体工程遇上 LLM 与 MCP
  • Spring Boot 自定义 Starter 机制
  • GPT-5.6 Sol预览解读:max推理、ultra多Agent与分层安全栈
  • 剑指offer-79、最⻓不含重复字符的
  • Codex Linux 教程:从安装配置到卸载清理全流程指南
  • 基于Anthropic-Cybersecurity-Skills构建网络安全AI智能体实战指南
  • FontForge字体设计完全指南:从入门到精通掌握专业字体编辑
  • GPT-5.6系列模型发布遇阻:OpenAI面临多国监管审批,Claude Fable 5重返引发全球讨论
  • Vibe Coding 实战复盘:一个人 + AI,从零打造会聊天的个人主页
  • 关于多线程归并排序的性能瓶颈与优化方案的技术7
  • HFSS求解设置实战解析:从驱动求解到本征模求解的核心配置
  • 数据中心电力模块的发展趋势对数据中心建设有哪些影响?
  • 目前自动评价系统问题---------会卡在一些异常的地方
  • XCP协议:从总线标定到汽车ECU数据交互的核心
  • GoChatIAI -Go语言AI应用服务平台(1)