JavaScript异步编程——11-异常处理方案【万字长文,感谢支持】

news2024/7/6 19:47:30

异常处理方案

在JS开发中,处理异常包括两步:先抛出异常,然后捕获异常。

为什么要做异常处理

异常处理非常重要,至少有以下几个原因:

  1. 防止程序报错甚至停止运行:当代码执行过程中发生错误或异常时,如果没有适当的异常处理机制,程序可能会报错、停止运行,甚至崩溃。通过处理异常,我们可以捕获错误并采取适当的措施避免系统报错。

  2. 错误排查和调试:异常处理有助于定位和排查错误。可以通过捕获异常并输出相关信息,比如打印日志、错误上报、跟踪堆栈等等,以便快速定位问题所在,并进行调试和修复。

  3. 提高代码健壮性和可靠性:可以采取适当的措施处理潜在的异常情况,从而减少程序出错的可能性。

  4. 提升用户体验:通过兜底、容错、容灾等异常处理方案,可以向用户提供有效的错误信息提示,而不是让用户界面无响应甚至白屏。

抛出异常

抛出异常的使用场景举例:

我们经常会封装一些工具函数,这些函数可能给自己用,也可能给外部团队用。

在函数内部,如果不符合预期的业务逻辑,或者遇到异常情况时,很多人的写法是直接 return,不往下执行了。但是 return 的写法存在一个很大的弊端:调用者不知道是因为函数内部没有正常执行,还是执行的返回结果就是一个undefined。return 的写法只是规避了问题,没有解决问题。建议的做法是:我们需要手动抛出异常

捕获异常

如果只是抛出异常,而不捕获异常的话,是比较危险的。这意味着当前任务立即终止,不再执行(当然,后续的其他任务会正常执行)。此外,这个异常信息会层层往上,抛给上层的调用者。如果一直未被捕获,则最终会抛给浏览器,浏览器控制台就会报错。

接下来,我们看一下不同代码场景下的异常处理方案。

上报异常

如果有必要的话,你可以把异常信息和日志,上报给监控服务器,然后集中分析。我每天上班第一件事,就是打开监控系统,看错误日志,然后对症下药解决问题。

同步代码的异常处理

通过 throw 抛出异常

我们可以通过 throw关键字,抛出一个用户自定义的异常。当代码执行时遇到 throw 语句时,当前函数会停止停止,即:当前函数 throw 后面的代码不会再执行。

throw 意思是,告诉调用者,当前被调用的函数报错了,调用者接下来需要捕获异常或者修改代码逻辑。

可以在 throw 的后面添加表达式或者数据类型,将添加的内容抛出去。数据类型可以是:number、string、boolean、对象等。

代码举例:

 function sum(num1, num2) {
   if (typeof num1 !== "number") {
     throw "type error: num1传入的类型有问题, 必须是number类型"
   }
 ​
   if (typeof num2 !== "number") {
     throw "type error: num2传入的类型有问题, 必须是number类型"
   }
 ​
   return num1 + num2
 }
 ​
 sum('a', 'b');

打印结果:

image-20230608180755608

当然,我们还可以 throw一个封装好的对象。比如:

 class myError {
   constructor(errCode, errMsg) {
     this.errCode = errMsg;
     this.errMsg = errMsg;
   }
 }
 ​
 function foo() {
   throw new myError(-1, 'not login');
 }
 ​
 foo();

上面这种写法比较麻烦,一般不这么写。其实,JS中已经内置了 Error 类,专门用于生成错误信息。

Error 类

JS内置的 Error 类非常好用。

代码举例:

 function foo() {
   throw new Error('not login');
 }
 ​
 ​
 foo();

打印结果:

image-20230608180103263

上面的打印结果可以看到,通过 Error 抛出来的错误,不仅可以看到报错信息,还可以看到调用栈,便于快速定位问题所在。非常方便。

通过 try catch 捕获异常

同步代码,只抛出异常,不捕获异常的代码举例:

 function foo() {
   throw new Error('not login');
 }
 ​
 foo();
 // 当前任务立即终止,不再执行;下面这行代码和 foo() 都在同一个 同步任务 中
 console.log('qianguyihao');

打印结果:

image-20230608182003407

可以看到,最后一行的 log 并没有执行。

我们可以使用 try catch 抛出异常, 对上述代码进行改进。代码举例:

 function foo() {
   throw new Error('not login');
 }
 ​
 // 通过 try catch 手动捕获异常
 try {
   foo();
 } catch (err) {
   console.log(err);
 }
 ​
 // 当前任务的后续代码会继续执行
 console.log('qianguyihao');

打印结果:

image-20230608182140002

通过 try catch finally 捕获异常

如果有些代码必须要执行,我们可以放到 finally 里。

  • 不管是否遇到异常,finally的代码一定会执行。

  • 如果 try 和 finally 中都有返回值,那么会使用finally中的返回值。

代码举例:

 function foo() {
   throw new Error('not login');
 }
 ​
 // 通过 try catch 捕获异常
 try {
   foo();
 } catch (err) {
   console.log(err);
 } finally {
   console.log("finally")
 }
 ​
 // 后续代码会继续执行
 console.log('qianguyihao');

try catch 只能捕获同步代码的异常

try catch只能捕获同步代码里的异常,而 Promise.reject() 是异步代码。

原因是:当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在 try-catch 所在的栈,所以 try-catch 并不能捕获到错误。对于微任务而言(比如 promise)promise 的构造函数的异常只能被自带的 reject 也就是.catch 函数捕获到。

参考链接:

  • try-catch 能抛出 promise 的异常吗

使用 window.onerror 监听未被捕获的代码异常

如果JS代码抛出了异常但没有进行捕获,我们可以使用 JS 自带的 window.onerror 事件监听到这些错误。

代码举例:

 // 监听同步代码的异常
 window.onerror = (event) => {
   console.error('onerror 监听到未被捕获的异常:', event)
 };
 ​
 function foo1() {
   throw new Error('not login');
 }
 ​
 function foo2() {
   throw new Error('network error');
 }
 ​
 foo1();
 foo2();

打印结果:

image-20230624162123559

Promise 的异常处理

reject() 会自动抛出异常

在使用 Promise 时,当我们调用了 reject() 之后,系统会自动抛出异常,不需要我们手动抛出异常。这是 Promise的内部机制。但是我们需要手动捕获异常。

当 Promise 进入 rejected状态之后,会触发 catch()方法的执行,捕获异常。此时,成功完成了Promise异常处理的闭环。

在then() 中抛出异常(重要)

当then()方法传入的回调函数中,如果遇到异常或者手动抛出异常,那么,then()所返回的新的 Promise 会进入rejected 状态,进而触发新Promise 的 catch() 方法的执行,做异常捕获。

场景1:在then()方法传入的回调函数中,如果代码在执行时遇到异常,系统会自动抛出异常。此时我们需要在 catch() 里手动捕获异常,否则会报错。

自动抛出异常的代码举例:(由于没有捕获异常,所以会报错)

 const myPromise = new Promise((resolve, reject) => {
   resolve('qianguyihao1 fulfilled');
 });
 ​
 myPromise.then(res => {
   console.log('res1:', res);
   // 显然,person 并没有 forEach()方法。所以,代码在执行时,会遇到异常。
   const person = { name: 'vae' };
   person.forEach(item => {
     console.log('item:', item);
   })
   // 这行代码不会执行,因为上面的代码报错了
   console.log('qianguyihao2');
 }).then(res => {
   console.log('res2:', res);
 })
 ​
 // 定时器里的代码正常执行
 setTimeout(() => {
   console.log('qianguyihao3');
 }, 100)

运行结果:

image-20230615090007932

代码改进:(代码在执行时遇到异常,此时我们捕获异常,所以系统不会报错,这才是推荐的写法)

const myPromise = new Promise((resolve, reject) => {
  resolve('qianguyihao1 fulfilled');
});

myPromise.then(res => {
  console.log('res1:', res);
  // 显然,person 并没有 forEach()方法。所以,代码在执行时,会遇到异常。
  const person = { name: 'vae' };
  person.forEach(item => {
    console.log('item:', item);
  })
  // 这行代码不会执行,因为上面的代码报错了
  console.log('qianguyihao2');
}).then(res => {
  console.log('res2:', res);
}).catch(err => {
  // 在 catch()方法传入的会调函数里,捕获异常
  console.log('err2:', err);
})

// 定时器里的代码正常执行
setTimeout(() => {
  console.log('qianguyihao3');
}, 100)

打印结果:

image-20230624072927944

场景2:在then()方法传入的回调函数中,如果我们手动抛出异常,此时我们需要在 catch() 里手动捕获异常,否则会报错。

代码举例:(手动抛出异常,未捕获,所以会报错)

const myPromise = new Promise((resolve, reject) => {
  resolve('qianguyihao fulfilled 1');
});

myPromise.then(res => {
  console.log('res1:', res);
  // 手动抛出异常
  throw new Error('qianguyihao rejected 2')
}).then(res => {
  console.log('res2:', res);
})

// 定时器里的代码正常执行
setTimeout(() => {
  console.log('qianguyihao3');
}, 100)

打印结果:

image-20230624073252797

代码改进:(代码在执行时遇到异常,此时我们捕获异常,所以系统不会报错,这才是推荐的写法)

const myPromise = new Promise((resolve, reject) => {
  resolve('qianguyihao fulfilled 1');
});

myPromise.then(res => {
  console.log('res1:', res);
  // 手动抛出异常
  throw new Error('qianguyihao rejected 2')
}).then(res => {
  console.log('res2:', res);
}, (err) => {
  console.log('err2:', err);
})

// 定时器里的代码正常执行
setTimeout(() => {
  console.log('qianguyihao3');
}, 100)

打印结果:

image-20230624073604599

使用 unhandledrejection 事件监听未被捕获的Promise异常

如果Promise抛出了异常但没有进行捕获,我们可以使用JS自带的 unhandledrejection 事件监听到这些错误。这个事件非常有用,尤其是当我们需要集中做日志收集时,屡试不爽。这个事件只能用于监听 Promise 中的异常,不能用于其他同步代码的异常。

先来看下面这行代码:

const myPromise = new Promise((resolve, reject) => {
  console.log('qianguyihao1');
  reject('not login');
  console.log('qianguyihao2');
})

打印结果:

image-20230624154609747

上面的代码抛出了异常,但没有捕获异常,所以我们可以用 unhandledrejection 事件监听到。代码举例:

// 监听未被捕获的 Promise 异常
window.addEventListener('unhandledrejection', (event) => {
  console.error(`unhandledrejection 监听到异常,写法1: ${event.reason}`)
});

window.onunhandledrejection = event => {
  console.error(`unhandledrejection 监听到异常,写法2: ${event.reason}`);
};

window.onerror = (event) => {
  console.error('onerror 监听到异常:', event);
};


const promise1 = new Promise((resolve, reject) => {
  reject('not login');
})

const promise2 = new Promise((resolve, reject) => {
  throw new Error('network error');
  resolve();
})

打印结果:

image-20230624172634569

可以看到,promise1 和 Promise2 的异常,都被 unhandledrejection 事件收集到了。

代码举例2:

window.addEventListener('unhandledrejection', (event) => {
  console.error(`unhandledrejection 监听到异常: ${event.reason}`)
});

window.onerror = (event) => {
  console.error('onerror 监听到异常:', event);
};

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error('not login');
    resolve();
  }, 100);
})

打印结果:

image-20230624172350994

上面的代码中,unhandledrejection 无法监听异常,因为定时器里的代码属于宏任务。

resolve()之后,再报错无效

代码举例:

const myPromise = new Promise((resolve, reject) => {
  resolve('fulfilled');
  throw new Error("自定义错误");
});

myPromise.then(res => {
  console.log("res", res);
  return res + 1;
}).catch(err => {
  console.log("err:", err);
});

打印结果:

res fulfilled

上方代码中,第3行的异常代码相当于没写。因为 resolve()之后,Promise的状态会立即进入 fulfilled,然后走到 then(),状态不可逆。

async await 的异常处理

捕获异常

代码举例:

function requestData1() {
  return new Promise((resolve, reject) => {
    reject('任务1失败');
  })
}

function requestData2() {
  return new Promise((resolve, reject) => {
    resolve('任务2成功');
  })
}

async function getData() {
  // requestData1 在执行时,遇到异常
  await requestData1();
  /*
  由于上面的代码在执行是遇到异常,所以,这里虽然什么都没写,底层默认写了如下代码:
  return Promise.reject('任务1失败');
  */

  // 下面这两行代码不会再执行了,因为上面的代码遇到了异常
  console.log('qianguyihao1');
  await requestData2();
}

getData();

// 【注意】定时器里的代码会正常实行,因为它在另外一个宏任务里,不在上面的微任务里
setTimeout(() => {
  console.log('qianguyihao2');
}, 100)

打印结果:

image-20230615085743284

所以,为了避免上述问题,我们还需要手动捕获异常。我们捕获到异常之后,这个异常就不会继续网上抛了,更不会抛给浏览器。

高级用法

###

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1676942.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字人解决方案——ID-Animator可保持角色一致生成视频动画

一、引 言 个性化或自定义生成在图像和视频生成领域是一个不断发展的研究方向,尤其是在创建与特定身份或风格一致的内容方面。您提到的挑战和解决方案为这一领域提供了有价值的见解: 训练成本高:这是一个普遍问题,因为个性化生成…

手机触控面板中应用的电容式触摸芯片

手机触控屏(Touch panel)又称为触控面板,是个可接收触头等输入讯号的感应式液晶显示装置,当接触了屏幕上的图形按钮时,屏幕上的触觉反馈系统可根据预先编程的程式驱动各种连结装置,可用以取代机械式的按钮面…

【AI】DeepStream(03):deepstream_test1_app

1、简介 deepstream-test1:演示各种 DeepStream 插件构建 GStreamer 管道。从文件中获取视频、解码、批处理,然后进行对象检测,最后在屏幕上渲染框。 源码路径:/opt/nvidia/deepstream/deepstream/sources/apps/sample_apps/deepstream-test1 先看下效果 2、编译 1)…

Redis-分片集群存储及读取数据详解

文章目录 Redis分片集群是什么?Redis分片集群的存储及读取数据? 更多相关内容可查看 Redis分片集群是什么? Redis分片集群是一种分布式部署方式,通过将数据分散存储在多个Redis节点上,从而提高了系统的性能、扩展性和…

【微记录】linux内核态日志如何持续观测?以及dmesg如何显示年月日时间戳?(dmesg -w ; -T)

文章目录 持续观测方法1方法2 dmes显示时间戳 持续观测 方法1 dmesg -w参考:https://man7.org/linux/man-pages/man1/dmesg.1.html 方法2 tail -f /var/log/kern.logdmes显示时间戳 dmesg -T #按照人类可读性高的时间戳 比如2024-05-15 01:20:16实操&#xff1…

快速学习SpringAi

Spring AI是AI工程师的一个应用框架,它提供了一个友好的API和开发AI应用的抽象,旨在简化AI应用的开发工序,例如开发一款基于ChatGPT的对话应用程序。通过使用Spring Ai使我们更简单直接使用chatgpt 1.创建项目 jdk17 引入依赖 2.依赖配置 …

敏捷开发最佳实践:自驱团队实践案例之心情曲线回顾会

调研发现,26%的中国企业认为最有价值管理实践是“团队回顾会”,而“团队回顾会”的确能够很好的引导团队走向自驱。在本节的实践案例中 “心情曲线回顾会”的具体做法较为典型,很值得参考。 本实践节选自《2021中国企业敏捷实践白皮书》&…

压力给到 Google,OpenAI 发布 GPT-4o 来了

北京时间5月14日凌晨1点,OpenAI 开启了今年的第一次直播,根据官方消息,这次旨在演示 ChatGPT 和 GPT-4 的升级内容。在早些时候 Sam Altman 在 X 上已经明确,「我们一直在努力开发一些我们认为人们会喜欢的新东西,对我…

OpenNJet产品体验:探索无限可能

文章目录 前言一、OpenNJet是什么?二、OpenNJet特性和优点三、OpenNJet功能规划四、OpenNJet快速上手五、OpenNJet的使用总结 前言 现代社会网络高速发展,同时也迎来了互联网发展的高峰,OpenNJet作为一个基于NGINX的面向互联网和云原生应用提…

爬虫入门经典(七) | 采集淘宝电场相关信息

大家好,我是不温卜火,昵称来源于成语—不温不火,本意是希望自己性情温和。 PS:由于现在越来越多的人未经本人同意直接爬取博主本人文章,博主在此特别声明:未经本人允许,禁止转载!&a…

string功能介绍(普及版)

目录 1。初始化(好几种方式),npos和string的使用说明 2。string的拷贝,隐式类型转换,[],size,iterator,begin,end,reverse,reverse_iterator&am…

【回溯】1240. 铺瓷砖

本文涉及知识点 回溯 LeetCode1240. 铺瓷砖 你是一位施工队的工长,根据设计师的要求准备为一套设计风格独特的房子进行室内装修。 房子的客厅大小为 n x m,为保持极简的风格,需要使用尽可能少的 正方形 瓷砖来铺盖地面。 假设正方形瓷砖的…

windows驱动开发-PCI和中断(二)

谈到中断使用PCI总线来作为例子是最合适的,在Windows发展过程中,PCI作为最成功的底层总线,集成了大量的外设,不夸张的说,目前PCI几乎是唯一的总线选择,故大部分情况下,只有PCI设备驱动程序会遇到…

前端 performance api使用 —— mark、measure计算vue3页面echarts渲染时间

文章目录 ⭐前言💖vue3系列文章 ⭐Performance api计算持续时间💖 mark用法💖 measure用法 ⭐计算echarts渲染的持续时间⭐结束 ⭐前言 大家好,我是yma16,本文分享关于 前端 performance api使用 —— mark、measure计…

java springboot连接sqlserver使用

pom.xml增加sqlserver驱动 <dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>mssql-jdbc</artifactId><version>9.4.0.jre8</version></dependency>application.yml配置文件 server:port: 9001 #spring: …

多格式兼容的在线原型查看:Axure RP的便捷解决方案

Axure rp不仅可以绘制详细的产品构思&#xff0c;还可以在浏览器中生成html页面&#xff0c;但需要安装插件才能打开。安装Axure后 rpchrome插件后&#xff0c;还需要在扩展程序中选择“允许访问文件网站”&#xff0c;否则无法在Axure中成功选择 rp在线查看原型。听起来很麻烦…

用友GRP-U8 userInfoWeb SQL注入致RCE漏洞复现 (XVE-2024-10539)

0x01 产品简介 用友GRP-U8R10行政事业内控管理软件是用友公司专注于国家电子政务事业,基于云计算技术所推出的新一代产品,是我国行政事业财务领域最专业的政府财务管理软件。 0x02 漏洞概述 用友GRP-U8R10行政事业内控管理软件 userInfoWeb接口处存在SQL注入漏洞,未授权的…

不懂数字后端Box List、Polygon的意思?

什么是BOX&#xff1f; 景芯SoC做design planning的第一步就是确定floorplan的box&#xff0c;也就是设计的区域。这个区域可以划分为三个边界&#xff0c;如下图所示&#xff1a; Die Box 最外面一圈&#xff0c;我们称为 Die Box&#xff0c;也就是用来放置 IO 单元&#x…

高中数学:平面向量-加减运算

一、向量的加法运算 三角形法则&#xff08;推荐&#xff09; 两个或多个向量收尾相连的加法运算&#xff0c;用三角形法则 简便算法 首尾相连的多个向量&#xff0c;去掉中间点&#xff0c;就是最终的和。 也可以用三角形法则证明 向量加法交换律 向量加法结合律 平行四…

pycharm如何有效读取到win10设置的环境变量

参考链接&#xff1a; 参考文章 该参考文章的第一种方法&#xff1a;设置win10环境变量。 在设置完环境变量后&#xff0c;在pycharm终端上不能有效读取到刚刚设置的环境变量的&#xff0c;需要启动win的cmd&#xff0c;在项目路径下执行脚本。如下所示的对比&#xff1a; cm…