JavaScript--错误处理

发布时间:2026/6/30 2:36:49

JavaScript--错误处理 一错误处理try...catch1.1 语法try { // 代码... } catch (err) { // 错误捕获 }它按照以下步骤执行首先执行try {...}中的代码。如果这里没有错误则忽略catch (err)执行到try的末尾并跳过catch继续执行。如果这里出现错误则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对于所有内建的 errorerror 对象具有两个主要属性nameError 名称。例如对于一个未定义的变量名称是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来处理这个 errorletjson { 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(doesnt execute); }这里JSON.parse正常执行但缺少name属性对我们来说确实是个 error。为了统一进行 error 处理我们将使用throw操作符。throw的语法throwerrorobject技术上讲我们可以将任何东西用作 error 对象。甚至可以是一个原始类型数据例如数字或字符串但最好使用对象最好使用具有name和message属性的对象某种程度上保持与内建 error 的兼容性。JavaScript 中有很多内建的标准 error 的构造器ErrorSyntaxErrorReferenceErrorTypeError等。我们也可以使用它们来创建 error 对象。leterror newError(message);// 或leterror newSyntaxError(message);leterror newReferenceError(message);// ...让我们来看看JSON.parse会生成什么样的 errortry{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”技术可以被更详细地解释为Catch 捕获所有 error。在catch (err) {...}块中我们对 error 对象err进行分析。如果我们不知道如何处理它那我们就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;// 再次抛出 (*)} }调用readData(触发内部try块中的错误执行到blabla();时因为blabla这个函数在全局作用域中根本不存在JavaScript 引擎会立刻抛出ReferenceError引用错误。try块内的代码执行就此中断直接跳转到内部的catch块。进入内部catch (err)块进行错误筛查此时捕获到的err是ReferenceError。代码执行判断if (!(err instanceof SyntaxError))内部错误“重新抛出”Rethrow因为判断条件为真if内部的throw err;被执行。内部的catch觉得自己没法处理这个ReferenceError它只认识SyntaxError于是把这个错误原封不动地向上抛,readData()函数执行被强制终止并把这个错误传到了外面。外部的try...catch接盘代码回到全局作用域外层的try { readData(); }检测到了从内部抛出的ReferenceError。外层的catch (err)成功捕获到这个错误。最终执行结果执行外层的alert( External catch got: err );。浏览器弹出警告框显示捕获到的错误信息。catch只应该处理你明确知道怎么处理的错误。如果遇到你无法识别或无法解决的错误绝对不要对它保持沉默不要写空的catch块什么都不做而应该使用throw err把它抛出去让调用该函数的外部代码或者全局错误监听器来处理。这能防止程序“静默失败”避免留下难以排查的 Bug。1.6 try.....catch........finallytry { ... 尝试执行的代码 ... } 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扩展 Error2.1拓展ErrorError类是内建的但我们可以通过下面这段近似代码理解我们要扩展的内容// 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 ); // PropertyRequiredError2.2包装异常将来函数readUser可能会不断壮大并可能会产生其他类型的 error。我们是否真的想每次都一一检查所有的 error 类型通常答案是 “No”我们希望能够“比它高一个级别”。我们只想知道这里是否是“数据读取异常” —— 为什么发生了这样的 error 通常是无关紧要的我们所描述的这项技术被称为“包装异常”。我们将创建一个新的类ReadError来表示一般的“数据读取” error。函数readUser将捕获内部发生的数据读取 error例如ValidationError和SyntaxError并生成一个ReadError来进行替代。对象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; } }

相关新闻