前端开发一直有种错觉,好像异常捕获都是后端的事,毕竟后端开发时如果不处理代码中的异常,有些资源得不到释放,极其容易导致内存泄漏。
前端由于 JS 的垃圾回收机制无需手动释放资源,反而不会怎么使用异常捕获代码中的错误。实际上任何编程语言,要写出一个健壮性的代码,都需要考虑异常处理。
本文分析下哪些地方可能需要使用异常捕获~~
为什么需要 try...catch
看一个例子:- (() => {
- function init() {
- a()
- b()
- c()
- }
- function a() {
- console.log('a 方法执行')
- // 在执行 a 方法时候,代码有异常错误
- JSON.parse('{a:1}')
- }
- function b() {
- console.log('b 方法执行')
- }
- function c() {
- console.log('c 方法执行')
- }
- init()
- })()
复制代码 以上代码在 init 函数中执行了多个方法,但 a 方法在执行后,由于 JSON.parse 格式化的数据有问题会代码报错,从而导致 b 和 c 方法无法执行。
而预期的执行逻辑是 a 方法执行完毕后,继续执行 b 和 c 方法。实际结果:
b 和 c 方法都未执行,而且用户也无法感知到任何异常,如果此方法放在了一个点击交互中,就会出现点击后没任何反馈,这肯定不是产品要的需求效果!!
稍稍优化:- (() => {
- function init() {
- a()
- b()
- c()
- }
- function a() {
- console.log('a 方法执行')
- try {
- // 在执行 a 方法时候,代码有异常错误
- JSON.parse('{a:1}')
- } catch (error) {
- // 给出用户提示
- alert('程序异常,请联系管理员')
- throw new Error('JSON.parse 错误')
- // 或直接使用 throw error 抛出
- }
- }
- function b() {
- console.log('b 方法执行')
- }
- function c() {
- console.log('c 方法执行')
- }
- init()
- })()
复制代码 优化后虽然代码还是无法执行 b 和 c 方法,但用户会感知到异常错误,对用户来说,至少有了一个交互反馈。
如果逻辑上 a 和 b、c 方法没有关联,那么 a 方法可以不用 throw 抛出错误,直接 return 即可,这样可以保证 b 和 c 方法顺利执行,比如:- function a() {
- console.log('a 方法执行')
- try {
- // 在执行 a 方法时候,代码有异常错误
- JSON.parse('{a:1}')
- } catch (error) {
- // 给出用户提示
- alert('程序异常,请联系管理员')
- return false
- }
- }
复制代码 使用 try...catch
那么在 JS 中有哪些地方需要异常捕获呢??接下来看看各种场景~~
同步代码
这类问题通常用于输入与预期不匹配,导致代码运行错误,如果不处理异常,可能会导致大面积的页面白屏或者没有任何反馈。
比如上面例子中的 JSON.parse() 格式化 JSON 数据时候,正确示例:- try {
- JSON.parse(jsonString)
- } catch (error) {
- // 根据需要处理错误
- }
复制代码 async/await 的异步代码
如果 await 等待的 Promise 被 reject,那么代码也会抛出异常,如果不处理错误,也会出现没有交互反馈的样子。
网络请求是最常见的 await 错误,正确示例:- async function getData() {
- try {
- const res = await fetch('https://test.com/api')
- const data = await res.json()
- } catch (error) {
- // 根据需要处理错误
- }
- }
复制代码 访问不确定的属性或方法
这种情况通常是在访问后端返回数据的时候会出现异常,比如预期我们需要的是 string 类型,但后端返回的是 null,在针对 string 操作时候,就会导致代码异常。
正确示例:- function getUsers(res) {
- try {
- // res 是后端返回的数据
- const users = res.list.split(',')
- return users
- } catch (error) {
- // 根据需要处理错误
- }
- return []
- }
- getUsers({list: null})
复制代码 或者使用 JS 提供的 可选链操作符 优化:- function getUsers(res) {
- // res 是后端返回的数据
- return res?.list?.split(',') ?? []
- }
复制代码 执行三方代码
三方代码不确定性太大,在执行三方代码提供的 API 尤其需要注意异常处理。
正确示例:- [/code][size=4]浏览器的 API 调用[/size]
- 浏览器提供的 API 也不全是能调用的,如果有版本兼容问题,在执行时候,代码也会出现异常错误。
- 以 localStorage 为例,如果用户禁用了本地存储,就有可能导致方法报错。记得 [b]无痕模式[/b] 刚开始兴起那会儿,在无痕模式下访问本地存储就会报错!!
- 正确示例:
- [code]try {
- localStorage.setItem('name', '前端路引');
- } catch (error) {
- // 根据需要处理错误
- }
复制代码 nodejs 文件操作
在服务端运行 nodejs 读取文件时候,尤其需要小心文件不存在时的异常问题。
正确示例:- try {
- const content = fs.readFileSync('web3dev.txt', 'utf8');
- } catch (error) {
- // 根据需要处理错误
- }
复制代码 用户输入校验
这种情况一般用于优化代码结构,直接在校验失败时候抛出异常,使用异常捕获统一处理提示信息。
正确示例:- function validateInput(input) {
- if (!input) {
- throw new Error('输入不能为空');
- }
- return true;
- }
- function isPhoneNumber(phone) {
- if (/^1[3456789]\d{9}$/.test(phone)) {
- return true;
- }
- throw new Error('请输入正确的手机号码');
- }
- try {
- const str = '1
- 2
- 3456789'
- validateInput(str);
- isPhoneNumber(str);
- // ... 后续的代码逻辑
- } catch (error) {
- alert(error.message);
- // 根据需要处理错误
- }
复制代码 try...catch 其他用法
try 的用法除了最常见的 try...catch 写法外,还有一些其他写法,比如:
省略 error
- try {
- JSON.parse('{a:1}')
- } catch {
- // 根据需要处理错误
- }
复制代码 使用 finally
无论 try 或 catch 的代码中是否报错,也无论是否有 return 和 throw 关键字。finally 的代码块都会正常执行,常用于资源释放,比如关闭文件流、处理数据库连接、关闭页面 loading 等。- function init() {
- try {
- console.log(1)
- JSON.parse('{a:1}')
- } catch {
- console.log(2)
- // 根据需要处理错误
- return false
- } finally {
- console.log(3)
- }
- }
- init()
复制代码 执行顺序:省略 catch
虽然 catch 可以省略,但缺少了 catch 捕获异常之后,代码不会往下执行,finally 的代码块不影响,比如:- try {
- JSON.parse('{a:1}')
- } finally {
- // 根据需要处理错误
- console.log(1)
- }
- console.log(2)
复制代码 执行结果:
catch 和 finally 两个至少要有一个,否则代码报错。
异步任务异常错误
try...catch 无法捕获异步任务中的异常错误,比如 Promise 中的异常错误,必须使用 Promise.catch 来捕获;setTimeout 的内部异常也无法使用 try 处理。- try {
- new Promise((resolve, reject) => {
- throw new Error("测试错误");
- }).catch((err) => {
- // 这里代码会执行
- console.log('Promise 内部错误', err);
- })
- } catch {
- console.log('不会执行')
- }
- try {
- setTimeout(() => {
- throw new Error("测试错误");
- }, 100);
- } catch {
- console.log('不会执行')
- }
复制代码 写在最后
任何事物都是犹过不及,合理使用 try...catch 可以有效提高代码健壮性,但过渡使用也容易造成代码冗余,所以编码也需要考虑分寸,拿捏得合适,则是优雅舞者。
编码注意:不要静默吞掉错误,至少使用 console 记录错误信息(如 console.error),否则会影响程序问题排查。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |