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

异常处理方案

在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.mzph.cn/web/12382.shtml

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

相关文章

虚拟化技术 在vCenter Server创建数中心、添加主机

一、实验内容 1.安装Flash 2.在vCenter Server创建数中心、添加主机 二、实验主要仪器设备及器材 1.安装有64位Windows操作系统的台式电脑或笔记本电脑,建议4C8G或以上配置 2.在Windows Server 2008 R2已安装vCenter Server 3.Adobe Flash Player 12.0.0.70.e…

算法-卡尔曼滤波之卡尔曼滤波的第一个方程:状态更新方程

通过一个例子来引出卡尔曼滤波的状态更新方程; 这里系统状态是金条的重量; 为了估计系统的状态,我们可以多次测量金条的重量,然后求平均值; 其中估计值是所有测量值的平均值; 由于我们使用的是静态模型&am…

第十六节:图 (20节)

一 图的概念 1)由点的集合和边的集合构成 2)虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达 3)边上可能带有权值 二 图结构的表达 1)邻接表法 2)邻接矩阵法 3)除此之外还有其他众多…

【完整过程】Windows下记录PadleOCR训练自己的ocr模型

一、前期准备 1、代码 参考的博主使用的是2.6版本的 博主的paddleocr代码 下面这个是官方的,可能已经更新了(我用的是官网当前最新版) paddleocr的源代码 注意:最好把上面两个代码都下载下来,后面都会用到 参考博…

先有JVM还是先有垃圾回收器?

是先有垃圾回收器再有JVM呢,还是先有JVM再有垃圾回收器呢?或者是先有垃圾回收再有JVM呢?历史上还真是垃圾回收更早面世,垃圾回收最早起源于1960年诞生的LISP语言,Java只是支持垃圾回收的其中一种。下面我们就来刨析刨析…

免费思维13招之十一:利润型思维

免费思维13招之十一:利润型思维 免费思维的另一大战略思维——利润型思维。 什么是利润型思维呢?就是用后期的利润来支付现在的成本。也就是“花未来的钱,办现在的事”。 我们在销售自己的产品时候,最容易犯的一个件事,就是降价,我们先来看一个案例: 前几年,有一个卖…

3dmax材质库导入方法?3dmax云渲染速度体验

3ds Max 材质库包含多种素材,如金属、木材、布料和石材等,但用户在导入材质时常遇到问题。本文将介绍如何在3ds Max中成功导入材质,并探讨使用云渲染服务来加速渲染过程,提高项目效率。 一、3dmax材质库导入教程 自建材质导入方法…

【js】获取媒体流实现拍照功能,摄像头切换

<script setup>import {onMounted,reactive,ref} from vueconst videoConstraints reactive({width: 500,height: 300});let picArr reactive([])let videoNode ref(null)let show ref(true)let stream reactive({})onMounted(async () > {// 获取视频流&#xf…

RuoYi-Vue-Plus (Logback 和 logback-plus.xml 、p6spy)

项目后本地日志 一、logback依赖 打开最外层的 pom.xml,查看 SpringBoot的依赖配置。 <dependencyManagement><dependencies><!-- SpringBoot的依赖配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>s…

position:fixed无法固定到父盒子上面的解决方案

目录 问题如图所示&#xff1a; 下面是错误的代码&#xff1a; 解决方案1&#xff1a; 使用fixed固定定位固定到父元素&#xff1a; 解决方案2&#xff1a; 推荐使用的其他方案&#xff08;粘性定位&#xff09;&#xff1a; 什么是粘性定位&#xff1a; 粘性定位的使用…

Leetcode—287. 寻找重复数【中等】(快慢指针算法)

2024每日刷题&#xff08;136&#xff09; Leetcode—287. 寻找重复数 快慢指针算法思想 low fast 时&#xff0c;快慢指针相遇&#xff0c;low 走过的距离是初始点&#xff08;0&#xff09;到环状开始的点 &#xff08;x&#xff09; 加上 环状开始的点&#xff08;x&…

LeetCode2390从字符串中移除星号

题目描述 给你一个包含若干星号 * 的字符串 s 。在一步操作中&#xff0c;你可以&#xff1a;选中 s 中的一个星号。移除星号 左侧 最近的那个 非星号 字符&#xff0c;并移除该星号自身。返回移除 所有 星号之后的字符串。注意&#xff1a;生成的输入保证总是可以执行题面中描…

详细分析Vue3中的ref(附Demo)

目录 前言1. 基本知识2. Demo 前言 由于新项目涉及Vue3&#xff0c;本着探究问题的本质研究所不会的疑问 1. 基本知识 ref 是 Vue 3 中用于创建响应式数据的函数 接收一个初始值并返回一个包含了该值的响应式引用对象与 Vue 2.x 中的 data 属性不同&#xff0c;ref 返回的是…

【已解决】力扣打不开

表现&#xff1a; 1.访问国内其他网站都没有问题 2.访问github也能成功 3.wifi没有问题 4.连接同网络的其他主机能打开 唯独力扣打不开&#xff0c;可能是DNS解析错误 》自己网络配置问题 解决办法【亲测可行】 找可用的hosts 打开站长之家&#xff0c;进行DNS查询&#xff…

卷积网络项目:实现识别鲜花四分类对比LeNet5、VGG16、ResNet18、ResNet34分类网络

卷积四分类项目 Gitee传送门 分类目标选取 鲜花 杏花 apricot_blossom桃花 peach_blossom梨花 pear_blossom梅花 plum_blossom 模型选择 卷积 LeNet5VGG16ResNet18ResNet34 以图搜图 获取相似度前10的搜图结果 数据清洗 鲜花四分类 删除非图片文件 删除重复图片 整理…

【JavaWeb】前后端分离SpringBoot项目快速排错指南

1 发起业务请求 打开浏览器开发者工具&#xff0c;同时显示网络&#xff08;Internet&#xff09;和控制台&#xff08;console&#xff09; 接着&#xff0c;清空控制台和网络的内容&#xff0c;如下图 然后&#xff0c;点击你的业务按钮&#xff0c;发起请求。 首先看控制台…

【C#进阶】简单数据结构类

简单数据结构类 文章目录 1、Arraylist1、ArrayList的本质2、声明3、增删查改4、装箱拆箱思考 背包售卖 2、Stack1、Stack的本质2、声明3、增取查改4、遍历思考 计算一个数的二进制 3、Queue1、Queue的本质2、声明3、增取查改4、遍历思考 每隔一段时间打印一条消息 4、Hashtab…

如何加密保护U盘?U盘加密方法盘点

U盘是目前最常用的移动存储设备&#xff0c;可以帮助我们存储大量数据。而为了保护数据安全&#xff0c;我们需要加密保护U盘。下面我们就来盘点一下U盘加密的方法。 BitLocker加密 BitLocker是Windows的一种磁盘保护工具&#xff0c;通过加密整个磁盘来保护数据&#xff0c;同…

基于Springboot的校园疫情防控信息管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园疫情防控信息管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…

Vscode编辑器 js 输入log自动补全

最近换了新电脑&#xff0c;新下载了Vscode&#xff0c;记录一下设置项。 Vscode 版本 想要的效果 js文件中输入log&#xff08;点击tab键&#xff09;&#xff0c;自动补全为 console.log() Vscode 文件》首选项》设置 搜索&#xff1a;snippets Emmet: Show Suggestions…