ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JavaScript 错误处理完全指南:try...catch 语法、throw 抛出、再次抛出与全局兜底

JavaScript 错误处理完全指南:try...catch 语法、throw 抛出、再次抛出与全局兜底 文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载无论代码写得多么严谨运行时错误用户输入异常、服务端响应错误、编程疏漏等总会不期而至。本文以现代 JavaScript 教程The Modern JavaScript Tutorial本仓库1-js/10-error-handling章节的try...catch主题为核心系统讲解错误捕获的语法结构、Error 对象、throw抛出自定义错误、再次抛出rethrowing模式、finally清理子句以及浏览器端的window.onerror全局兜底方案。读完本文你将能够为脚本构建一套从局部捕获到全局兜底的完整错误处理体系。try...catch 语法与执行流程try...catch是 JavaScript 提供的错误捕获语法结构它让脚本在出错时不再死亡立即停止并只在控制台打印错误而是有机会执行更合理的补救操作。结构由两部分组成try { // 代码... } catch (err) { // 错误捕获 }它按照以下步骤执行首先执行try {...}中的代码。如果这里没有错误则忽略catch (err)执行到try的末尾并跳过catch继续执行。如果这里出现错误则try执行停止控制流转向catch (err)的开头。变量err可以使用任何名称将包含一个 error 对象该对象包含了所发生事件的详细信息。整体流程可以用下图直观表示没有 error 的例子try { alert(开始执行 try 中的内容); // (1) // ...这里没有 error alert(try 中的内容执行完毕); // (2) } catch (err) { alert(catch 被忽略因为没有 error); // (3) }无错误时依次显示(1)和(2)的alertcatch完全被跳过。包含 error 的例子try { alert(开始执行 try 中的内容); // (1) lalala; // error变量未定义 alert(try 的末尾未执行到此处); // (2) } catch (err) { alert(出现了 error); // (3) }有错误时依次显示(1)和(3)try在出错处立即中断后续代码不再执行。try...catch 的两个关键边界边界一仅对运行时的 error 有效要使得try...catch能工作代码必须是可执行的。换句话说它必须是有效的 JavaScript 代码。如果代码包含语法错误那么try...catch将无法正常工作例如含有不匹配的花括号try { {{{{{{{{{{{{ } catch (err) { alert(引擎无法理解这段代码它是无效的); }JavaScript 引擎首先会读取代码然后运行它。在读取阶段发生的错误被称为解析时间parse-time错误并且无法恢复从该代码内部因为引擎根本无法理解这段代码。所以try...catch只能处理有效代码中出现的错误——这类错误被称为运行时的错误runtime errors有时也被称为异常exceptions。边界二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);仓库延伸异步代码中的捕获同样遵循这一原则。在 async/await 章节 中当 promise 被 reject 时await会像throw语句一样抛出 error此时可以用try..catch以与捕获常规throw相同的方式捕获它async function f() { try { let response await fetch(http://no-such-url); } catch(err) { alert(err); // TypeError: failed to fetch } } f();如果有 error 发生执行控制权会立即移交至catch块也可以用try包装多行await代码一次性捕获fetch与response.json()等环节的所有错误。可以看到把try...catch放在真正出错的异步回调内部这一原则在 Promise 与 async/await 时代依然成立只不过await把异步错误重新变成了同步的throw从而可以被try...catch正常接住。Error 对象发生错误时JavaScript 会生成一个包含有关此 error 详细信息的对象然后将该对象作为参数传递给catchtry { // ... } catch (err) { // -- error 对象也可以用其他参数名代替 err // ... }对于所有内建的 errorerror 对象具有两个主要属性属性说明nameError 名称。例如对于一个未定义的变量名称是ReferenceErrormessage关于 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 }可选的 catch 绑定如果我们不需要 error 的详细信息catch也可以忽略绑定直接写成catch {而不带参数括号try { // ... } catch { // -- 没有 (err) // ... }该语法适合只兜底、不深究的简单场景。实战场景用 try...catch 解析 JSONJavaScript 内建的JSON.parse(str)方法详见仓库中的 JSON 方法章节常被用来解析从网络、服务器或其他来源接收到的数据let json {name:John, age: 30}; // 来自服务器的数据 let user JSON.parse(json); // 将文本表示转换成 JavaScript 对象 // 现在 user 是一个解析自 json 字符串的有自己属性的对象 alert( user.name ); // John alert( user.age ); // 30如果json格式错误JSON.parse就会生成一个 error因此脚本就会死亡。如果放任不管当拿到的数据出了问题访问者永远都不会知道原因除非他们打开开发者控制台——代码执行失败却没有提示信息这是很糟糕的用户体验。让我们用try...catch来处理这个 errorlet json { bad json }; try { let user JSON.parse(json); // -- 当出现 error 时... alert( user.name ); // 不工作 } catch (err) { // ...执行会跳转到这里并继续执行 alert( 很抱歉数据有错误我们会尝试再请求一次。 ); alert( err.name ); alert( err.message ); }catch块并不仅限于显示信息可以发送一个新的网络请求、向访问者建议替代方案、把错误信息发送给日志记录设备……所有这些都比代码死掉好得多。抛出我们自定义的 error如果json在语法上是正确的但缺少必需的name属性呢let json { age: 30 }; // 不完整的数据 try { let user JSON.parse(json); // -- 没有 error alert( user.name ); // 没有 name } catch (err) { alert( doesnt execute ); }这里JSON.parse正常执行但缺少name属性对我们来说确实是个 error。为了统一进行 error 处理我们将使用throw操作符。throw 操作符throw操作符会生成一个 error 对象语法如下throw error object技术上讲我们可以将任何东西用作 error 对象甚至可以是一个原始类型数据数字或字符串但最好使用对象最好使用具有name和message属性的对象某种程度上保持与内建 error 的兼容性。JavaScript 中有很多内建的标准 error 构造器Error、SyntaxError、ReferenceError、TypeError等。它们的语法是let error new Error(message); // 或 let error new SyntaxError(message); let error new ReferenceError(message); // ...对于内建的 error仅对于 error不是对于其他任何对象name属性刚好就是构造器的名字message则来自于参数。例如let error new Error(Things happen o_O); alert(error.name); // Error alert(error.message); // Things happen o_O来看一下JSON.parse会生成什么样的 errortry { JSON.parse({ bad json o_O }); } catch(err) { alert(err.name); // SyntaxError alert(err.message); // Unexpected token b in JSON at position 2 }正如我们所看到的那是一个SyntaxError。实战数据缺失时抛出 SyntaxError在我们的示例中缺少name属性就是一个 error因为用户必须有一个name。所以让我们抛出这个 errorlet json { age: 30 }; // 不完整的数据 try { let user JSON.parse(json); // -- 没有 error if (!user.name) { throw new SyntaxError(数据不全没有 name); // (*) } alert( user.name ); } catch(err) { alert( JSON Error: err.message ); // JSON Error: 数据不全没有 name }在(*)标记的这一行throw操作符生成了包含着我们所给定message的SyntaxError与 JavaScript 自己生成的方式相同。try的执行立即停止控制流转向catch块。现在catch成为了所有 error 处理的唯一场所对JSON.parse和其他情况都适用。再次抛出Rethrowing在上面的例子中我们使用try...catch来处理不正确的数据。但try {...}块中也可能发生另一个预料之外的 error——例如编程错误未定义变量let json { age: 30 }; // 不完整的数据 try { user JSON.parse(json); // -- 忘记在 user 前放置 let // ... } catch (err) { alert(JSON Error: err); // JSON Error: ReferenceError: user is not defined // (实际上并没有 JSON Error) }try...catch旨在捕获数据不正确的 error但catch会捕获到所有来自try的 error。这里它捕获了一个预料之外的 error却仍然抛出同样的JSON Error信息——这是不正确的也会使代码变得更难以调试。为了避免此类问题可以采用重新抛出技术规则很简单catch应该只处理它知道的 error并抛出所有其他 error。再次抛出rethrowing技术可以更详细地解释为Catch 捕获所有 error。在catch (err) {...}块中对 error 对象err进行分析。如果我们不知道如何处理它那就throw err。通常我们可以使用instanceof操作符判断错误类型try { user { /*...*/ }; } catch (err) { if (err instanceof ReferenceError) { alert(ReferenceError); // 访问一个未定义undefined的变量产生了 ReferenceError } }我们还可以从err.name属性中获取错误的类名所有原生的错误都有这个属性另一种方式是读取err.constructor.name。在下面的代码中我们使用再次抛出让catch只处理SyntaxErrorlet json { age: 30 }; // 不完整的数据 try { let user JSON.parse(json); if (!user.name) { throw new SyntaxError(数据不全没有 name); } blabla(); // 预料之外的 error alert( user.name ); } catch (err) { if (err instanceof SyntaxError) { alert( JSON Error: err.message ); } else { throw err; // 再次抛出 (*) } }如果(*)标记的这行catch块中的 error 从try...catch中掉了出来那么它也可以被外部的try...catch结构如果存在捕获到如果外部不存在这种结构脚本就会被杀死。所以catch块实际上只处理它知道该如何处理的 error并跳过所有其他的 error。下面这个示例演示了这种类型的 error 是如何被另外一级try...catch捕获的function readData() { let json { age: 30 }; try { // ... blabla(); // error! } catch (err) { // ... if (!(err instanceof SyntaxError)) { throw err; // 再次抛出不知道如何处理它 } } } try { readData(); } catch (err) { alert( External catch got: err ); // 捕获了它 }上面的readData只知道如何处理SyntaxError而外部的try...catch知道如何处理任意的 error。这就是错误处理分层的思想每一层只处理自己职责范围内的错误其余的上抛给外层。try...catch...finallytry...catch结构还可以有一个代码子句clausefinally。如果它存在它在所有情况下都会被执行try之后如果没有 errorcatch之后如果有 error。扩展语法如下try { ... 尝试执行的代码 ... } catch (err) { ... 处理 error ... } finally { ... 总是会执行的代码 ... }试试运行这段代码try { alert( try ); if (confirm(Make an error?)) BAD_CODE(); } catch (err) { alert( catch ); } finally { alert( finally ); }这段代码有两种执行方式如果对 Make an error? 的回答是 Yes那么执行try - catch - finally。如果回答是 No那么执行try - finally。finally子句通常用在当我们开始做某事的时候希望无论出现什么情况都要完成某个任务。实战无论成败都完成计时例如我们想要测量一个斐波那契数字函数fib(n)执行所需要花费的时间。通常我们可以在运行它之前开始测量并在运行完成时结束测量。但是如果在该函数调用期间出现 error 该怎么办下面这段fib(n)的实现代码在遇到负数或非整数数字时会返回一个 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 );运行这段代码并在prompt弹窗中输入35——代码运行正常先执行try然后是finally如果输入-1——立即出现 error执行只花费0ms。以上两种情况下的时间测量都正确地完成了。换句话说函数fib以return还是throw完成都无关紧要两种情况下都会执行finally子句。变量作用域提示上面代码中的result和diff变量都是在try...catch之前声明的。否则如果使用let在try块中声明变量那么该变量将只在try块中可见finally中无法访问。finally 和 returnfinally子句适用于try...catch的任何出口这包括显式的return。在下面这个例子中try中有一个return此时finally会在控制转向外部代码前被执行function func() { try { return 1; } catch (err) { /* ... */ } finally { alert( finally ); } } alert( func() ); // 先执行 finally 中的 alert然后执行这个 alerttry...finally没有catch子句的try...finally结构也很有用当我们不想在原地处理 error让它们掉出去但需要确保我们启动的处理被完成时应当使用它function func() { // 开始执行需要被完成的操作比如测量 try { // ... } finally { // 完成前面我们需要完成的那件事即使 try 中的执行失败了 } }由于没有catch所以try中的 error 总是会使代码执行跳转至函数func()外但在跳出之前需要执行finally中的代码。仓库延伸为什么是finally而不是把代码写在try...catch之后本仓库为这一主题配备了配套练习 使用 finally 还是直接放在代码后面其官方解答明确指出当函数中存在跳出try..catch的行为时两种方式的表现截然不同。例如try中有return时finally会在try...catch刚刚执行完成后、调用代码获得控制权之前运行当catch中发生throw err时同样如此。而如果只是把清理代码放在函数末尾在这些提前退出的情况下它根本不会运行——这正是finally保证清理逻辑cleanup必然执行的价值所在。全局 catchwindow.onerror环境特定这个部分的内容并不是 JavaScript 核心的一部分。设想一下在try...catch结构外有一个致命的 error然后脚本死亡了。这个 error 可能是编程错误或其他可怕的事情。此时我们可能想要记录这个 error并向用户显示某些内容通常用户看不到错误信息。规范中没有相关内容但代码的执行环境一般会提供这种机制。在浏览器中我们可以将一个函数赋值给特殊的window.onerror属性该函数将在发生未捕获的 error 时执行Node.js 环境则对应process.on(uncaughtException)。语法如下window.onerror function(message, url, line, col, error) { // ... };参数说明messageerror 信息url发生 error 的脚本的 URLlinecol发生 error 处的代码的行号和列号errorerror 对象例如script window.onerror function(message, url, line, col, error) { alert(${message}\n At ${line}:${col} of ${url}); }; function readData() { badFunc(); // 啊出问题了 } readData(); /script全局错误处理程序window.onerror的作用通常不是恢复脚本的执行——如果发生编程错误恢复脚本的执行几乎是不可能的——它的作用是将错误信息发送给开发者。针对这种情况业界也存在专门的 error 日志 Web 服务它们通常这样运行注册服务拿到一段 JavaScript 代码或脚本的 URL然后插入到页面中。该 JavaScript 脚本设置自定义的window.onerror函数。当发生 error 时它会发送一个此 error 相关的网络请求到服务提供方。登录到服务方的 Web 界面查看这些 error。仓库延伸从 try...catch 到自定义 Error 层次体系try...catch配合throw与instanceof还可以进一步演化出自定义 Error 类这一进阶模式。在本仓库的 自定义 Error扩展 Error 章节中教程展示了如何让自定义错误支持message、name、stack等基本属性并携带statusCode之类的自有字段同时通过继承内建Error类来获得obj instanceof Error的识别能力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); // 一个嵌套调用的列表每个调用都有对应的行号 }请注意(1)行调用父类 constructorJavaScript 要求子类 constructor 中必须调用super父类 constructor 会把name设置为Error所以(2)行要将其重置为正确的值。借助instanceof一个try..catch块可以同时分派处理自定义的ValidationError与内建的SyntaxError并把未知 error 再次抛出——这正是再次抛出思想在类层次体系中的自然延伸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再次抛出 } }总结try...catch结构允许我们处理执行过程中出现的 error。从字面上看它允许尝试运行代码并捕获其中可能发生的 error。语法如下try { // 执行此处代码 } catch (err) { // 如果发生 error跳转至此处 // err 是一个 error 对象 } finally { // 无论怎样都会在 try/catch 之后执行 }这儿可能会没有catch或者没有finally所以try...catch或try...finally都是可用的。核心要点回顾Error 对象属性message人类可读的 error 信息、nameerror 名称字符串即 Error 构造器的名称、stack非标准但广受支持的调用栈信息。省略 error 对象不需要 error 对象时可以通过catch {而不是catch (err) {来省略绑定。throw 生成自定义 errorthrow的参数可以是任何东西但通常是继承自内建Error类的 error 对象详见 自定义 Error扩展 Error 章节。再次抛出rethrowingcatch块通常期望并知道如何处理特定的 error 类型因此它应该再次抛出它不知道的 error——这是错误处理分层的重要模式。全局兜底即使没有try...catch大多数执行环境也允许设置全局 error 处理程序来捕获掉出fall out的 error在浏览器中就是window.onerror。把局部精确捕获 未知错误上抛 全局兜底记录这套组合拳用好你的 JavaScript 脚本就能够在各种异常场景下保持可诊断、可恢复、可观测。赞分享文档教程前端【免费下载链接】zh.javascript.info现代 JavaScript 教程The Modern JavaScript Tutorial以最新的 ECMAScript 规范为基准通过简单但足够详细的内容为你讲解从基础到高阶的 JavaScript 相关知识。项目地址https://gitcode.com/gh_mirrors/zh/zh.javascript.info点击查看免费下载相关推荐JavaScript Promise 错误处理完全指南隐式 try..catch、错误重抛与 unhandledrejection 全局兜底JavaScript Promise 错误处理完全指南隐式 try..catch、错误重抛与 unhandledrejection 全局兜底 Promise文档/教程前端Modern JavaScript Tutorial 错误处理实战深入解析 try...catch、throw、重抛与 finallyModern JavaScript Tutorial 错误处理实战深入解析 try...catch、throw、重抛与 finally 无论多么熟练的开发者文档/教程前端Swift 错误处理完全指南throw、do-catch、try?/try! 与类型化抛错实战解析《Swift 编程语言》中文版Swift 错误处理完全指南throw、do catch、try?/try! 与类型化抛错实战解析《Swift 编程语言》中文版 Swift 的错误处理机文档教程上一篇如何用AMLL构建超越Apple Music的Web动态歌词体验下一篇WebdriverIO 与 Chrome DevTools Recorder零编码录制测试并导出为 WebdriverIO 测试脚本创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表