使用 async/await 是必须避免的陷阱

使用 async/await 是必须避免的陷阱

如果我们使用过 nodejs,那么我们可能已经在 javaSoript 中使用了异步操作。异步任务是一个独立于 JavaSoript 引擎的主线程执行的操作。从本质上讲,这就是应用程序功能没有阻塞的 UI 的原因。

nodejs 的单线程性质,这一点极其重要。

Node.js 利用事件循环来处理所有异步操作,保留了用于计算函数的主线程。

在这里插入图片描述
假设我们对事件循环有相当的了解。在这种情况下,我们会明白,当在调用堆栈中发现一个非同步操作时,JS会把它放到线程池上,线程池将通过 libuv 库异步地执行它。之后 libuv 将执行操作并将其推进到"事件队列"中。"事件队列将被持续监控,事件队列中的事件将被提取并在处理异步操作响应的回调函数上执行。这基本上就是 nodejs 如何处理异步操作。

例如我们可以使用 JavaSrispt 中 Promise 建立异步操作。Promise 返回的一个对象,代表其进程。

// 返回一个Promise对象
function fetchData() {return new Promise((resolve, reject) => {// 使用setTimeout 模拟一个异步操作setTimeout(() => {const data = 'Sample Data';const success = true; if (success) {resolve(data); } else {reject('Error: Unable to fetch data');}}, 2000);});
}
const fetchDataPromise = fetchData();
fetchDataPromise.then(data => {console.log('Data received:', data);
})
.catch(error => {console.error(error);
});

fetchData 方法返回的 Promise 对象包含两种方法:then 和 catch。开发者可以在这两个方法中获取结果。

这使 JavaSrhpt 更加强大,使我们能够构建实时聊天应用程序和API等应用程序。然而,在设计应用程序时,使用JavaSrispt异步操作会有一些常见的缺陷,我们必须考虑这些缺陷,以便能够实现缓解这些问题的方法。

注意:这些陷阱存在于任何 javascript 框架。

回调地狱

使用基于 Promise 的异步操作的关键问题之一是回调地狱。在这种情况下,回调会不断调用 Promise,导致回调链。例如:

function performAsyncOperation(delay: number, message: string): Promise<string> {return new Promise((resolve) => {setTimeout(() => {console.log(message);resolve(message);}, delay);});
}export async function callbacks() {const delay = 1000;const message = 'Hello World';return performAsyncOperation(delay, message).then((value) => {performAsyncOperation(delay, value).then((secondValue) => {performAsyncOperation(delay, secondValue).then((thirdValue) => {performAsyncOperation(delay, thirdValue).then(() => {console.log('End The Callback');}).catch(() => {console.log('Error');});;}).catch(() => {console.log('Error');});;}).catch(() => {console.log('Error');});});
}

callbacks() 方法 返回一个 performAsyncOperation 并继续添加更多的异步操作。虽然能在生产中发挥完美的作用。但是,当我们考虑到可维护性时,它将是一个混乱的问题。例如,很难看到什么样的回调应用在什么级别。

所以,我们如何避免这种情况?

为了修复回调地狱问题,我们可以将此转换为 async/await 。所以, 我们看看这个的更新代码 :

function performAsyncOperation(delay: number, message: string): Promise<string> {return new Promise((resolve) => {setTimeout(() => {console.log(message);resolve(message);}, delay);});
}export async function asyncAwait() {try {const delay = 1000;let message = 'Hello World';message = await performAsyncOperation(delay, message);message = await performAsyncOperation(delay, message);message = await performAsyncOperation(delay, message);await performAsyncOperation(delay, message);console.log('End The Callback');} catch (error) {console.log('Error:', error);}
}

我们已经成功地将回调地狱重构为更清洁的方法,它使用async/await,这允许我们执行相同的异步代码,而我们在早些时候执行了一个更干净的方法。await 意味着每一行代码在收到回复之前等候。如果它返回一个成功的响应,它将继续到下一个。但是如果它遇到错误,它将跳到公共的catch 整块。这样做可以避免维护多个错误处理程序和使用单个错误处理程序的需要。

同步函数链

我们已经重构我们的代码,使用async/await 块来处理多个异步调用。但是现在,我们可能会注意到这里有一个新问题:

function performAsyncOperation(delay: number, message: string): Promise<string> {return new Promise((resolve) => {setTimeout(() => {console.log(message);resolve(message);}, delay);});
}export async function issueAsyncAwait() {try {const delay = 1000;let message = 'Hello World';await performAsyncOperation(delay, message);console.log('Phase 01');await performAsyncOperation(delay, message);console.log('End The Callback');} catch (error) {console.log('Error:', error);}
}

在这种情况下,我们想执行 console.log(‘Phase 1’) ,但是performAsyncOperation 方法在一个单独的进程中执行,我们的打印应该是在performAsyncOperation 方法执行前完成对吗?

在这里插入图片描述

经过检查,我们可以看到这并不是我们所期待的。怎么回事?

顾名思义,它"等待"整个代码块,直到异步操作返回响应。因此,这使得我们的代码"同步",并创建了一个瀑布调用模式,在这里我们的代码将一个接一个地调用。

因此,如果我们的事件并不相互依赖,如果我们的事件不依赖于非同步操作的输出,我们不必一定要等到非同步操作完成,对吗?

所以,在这种情况下, 考虑使用回调 :

function performAsyncOperation(delay: number, message: string): Promise<string> {return new Promise((resolve) => {setTimeout(() => {console.log(message);resolve(message);}, delay);});
}export async function asyncAwaitFix() {try {const delay = 1000;let message = 'Hello World';performAsyncOperation(delay, message).then((resp) => console.log(`Process the resp: ${resp}.`));console.log('Phase 01');await performAsyncOperation(delay, message);console.log('End The Callback');} catch (error) {console.log('Error:', error);}
}

如你所见,我们重构了performAsyncOperation 方法并使用 .then() 回调。这样做可以让回调作为一个真正的回调执行,并且不会在代码中创建任何"等待"。为了验证我们的理论,让我们检查一下输出:

在这里插入图片描述
如你所见,Phase 01 首先打印了,不再等待到 async 操作完成。

但是要小心使用这个,因为我们可能会创建回调地狱!

循环的性能问题

接下来,让我们谈谈循环。我们都用 JavaScript 写过循环:

 for (let i = 0; i < 5; i++) {console.log('Iteration number:', i);}

我们循环了一组元素,并对其进行了一些计算。但是如果我们必须在这里执行异步操作呢?假设我们得到了一堆用户身份证。并被要求获取所有身份证的信息(注意:我们的API不支持批量)。 我们可能会这样写:

function getUserInfo(id: number) {return new Promise((resolve) => {// 模拟异步setTimeout(() => {resolve({ userId: id, name: `User_${id}`, email: `user${id}@example.com` });}, 1000);});
}export async function asyncForLoopIssue(userIds: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) {const usersInfo: any[] = [];for (let i = 0; i < userIds.length; i++) {const userInfo = await getUserInfo(userIds[i]);usersInfo.push(userInfo);}console.log({ usersInfo });return usersInfo;
}

现在,这个代码再次没有问题。它将按预期在生产中发挥作用。但是,我们被限制在这里的同步循环。这意味着一旦收集到单个用户信息,我们的循环的下一次迭代将开始。因此,这个函数将在10s后执行,并像这样的同步输出:

在这里插入图片描述
这是一个接一个发生的。

但我们该怎么解决?

可以用非线性来执行这个循环:

function getUserInfo(id: number) {return new Promise((resolve) => {setTimeout(() => {resolve({ userId: id, name: `User_${id}`, email: `user${id}@example.com` });}, 1000);});
}export async function loopAsyncFix(userIds: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) {const promises = userIds.map(async (id) => {const userInfo = await getUserInfo(id);return userInfo;})const usersInfo = await Promise.all(promises);console.log({ usersInfo });return usersInfo;
}

现在,这种方法将产生相同的响应。然而,它的实现方式有点不同。

在方法01中,每次迭代都在当前的async操作完成后开始。
async 意味着它应该在不干扰主线程的情况下执行。

第二种方法坚持真正的异步方法,因为它返回最终将执行的 Promise 对象。因此,虽然我们是顺序运行它,但是它将返回随机调用,每个调用都是独立的,并且在没有相关顺序的情况下按自己的速度执行。

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

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

相关文章

如何快速看懂市场行情?

一、看大盘指数 咱们平时所说的大盘其实指的就是上证指数&#xff0c;它是整个市场的晴雨表。大盘涨了&#xff0c;个股跟着上涨的概率就大&#xff0c;大盘跌了&#xff0c;个股被拖累下跌的概率也大。所以&#xff0c;要想在股市中尝到甜头&#xff0c;大盘分析是少不了滴&am…

python socket编程7 - 使用PyQt6 开发UI界面新增实现UDP server和client单机通讯的例子

在第五篇中&#xff0c;简单实现了命令行下的 TCP/UDP server和client的单机通讯。 在第六篇中&#xff0c;实现了PyQt6开发界面&#xff0c;TCP协议实现的单机server和client的通讯功能。 这一篇&#xff0c;在第六篇的基础上&#xff0c;增加了UDP server和client的单机通讯功…

【在英伟达nvidia的jetson-orin-nx上使用调试摄像头-同时开启多个摄像头-基础测试(2)】

【在英伟达nvidia的jetson-orin-nx上使用调试摄像头-同时开启多个摄像头-USB摄像头与Camera Conn.#0/#1接口-基础测试&#xff08;2&#xff09;】 1、概述2、实验环境3、 先前确认&#xff08;1&#xff09;USB摄像头&#xff0c;先确认可以单独打开&#xff08;2&#xff09;…

Leetcode题库(数据库合集)_ 难度:困难

目录 难度&#xff1a;困难1. 部门工资前三高的所有员工2. 行程和用户3. 体育馆的人流量4. 员工薪水的中位数5. 同一天的第一个电话和最后一个电话6. 查询员工的累计薪水7. 给定数字的频率查询中位数8. 学生地理信息报告9. Hopper 公司查询 ①10. 职员招聘人数11. 职员招聘人数…

MPC模型预测控制理论与实践

一、基本概念 最有控制的动机是在约束条件下达到最优的系统表现。 模型预测控制&#xff08;MPC&#xff0c;Model Predictive Control&#xff09;是通过模型来预测系统在某一未来时间段内的表现来进行优化控制&#xff0c;多用于数位控制&#xff0c;通常用离散型状态空间表…

python 堆与栈

【一】堆与栈 【 1 】简介 栈&#xff08;stack&#xff09;&#xff0c;有些地方称为堆栈&#xff0c;是一种容器&#xff0c;可存入数据元素、访问元素、删除元素&#xff0c;它的特点在于只能允许在容器的一端&#xff08;称为栈顶端指标&#xff0c;英语&#xff1a;top&a…

Java_ArrayList顺序表详解

目录 前言 顺序表 ​编辑 顺序表和数组 ArrayList简介 说明 ArrayList使用​编辑 ArrayList常见操作 ArrayList实现二维数组 ArrayList的遍历 ArrayList的扩容机制 总结 前言 一个高端的程序员,往往都是数据结构学的很好,判断一个程序的优劣也是看数据结构学的好与坏.…

原生video设置控制面板controls显示哪些控件

之前我们学习了如何使用原生video播放视频 今天来一个进阶版的——设置控制面板controls显示哪些控件 先看一下当我们使用原生video时&#xff0c;controls属性为true时&#xff0c;相关代码如下&#xff1a; 正常的控制面板默认显示的控件有&#xff1a;播放、时间线、音量调…

Android基础: 使用Java代码控制 Activity类页面相互之间进行跳转 Activity页面的的启动和结束

Android基础&#xff08;Activity&#xff09; Activity的启动和结束 我们主要看Java代码逻辑&#xff1a; 第一个页面的逻辑代码 public class StartActivity01 extends AppCompatActivity implements View.OnClickListener {Overrideprotected void onCreate(Bundle saved…

香港云服务器计算型和通用型的区别

在当今数字化时代&#xff0c;云服务器作为企业级应用的核心设备&#xff0c;其性能和类型对于企业的运营和数据处理至关重要。在常见的香港云服务器类型中&#xff0c;通用型和计算型是最为常见的两种。那么&#xff0c;这两种云服务器到底有什么区别呢? 设计目标和应用场景不…

HarmonyOS开发基础(一)

HarmonyOS开发基础&#xff08;一&#xff09; // &#xff1a;装饰器&#xff1a;用来装饰类结构、方法、变量 Entry // Entry&#xff1a;标记当前组件为入口组件 Component // Component&#xff1a;标记为自定义组件 // struct&#xff1a;自定义组件&#xff0c;可复用的…

【LeetCode】692. 前K个高频单词

692. 前K个高频单词 描述示例解题思路及事项思路一思路二 描述 给定一个单词列表 words 和一个整数 k &#xff0c;返回前 k 个出现次数最多的单词。 返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率&#xff0c; 按字典顺序 排序 示例 示例1 输…

Python实现FA萤火虫优化算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 萤火虫算法&#xff08;Fire-fly algorithm&#xff0c;FA&#xff09;由剑桥大学Yang于2009年提出 , …

15.Servlet [一篇通]

文章目录 1.Servlet 是什么2.第一个 Servlet 程序2.1创建项目2.2引入依赖2.3创建目录2.4编写代码2.5打包程序2.6部署程序2.7验证程序 3.更方便的部署方式3.1安装 Smart Tomcat 插件3.2配置 Smart Tomcat 插件 4.访问出错怎么办?4.1出现 4044.2出现 4054.3出现 5004.4出现 &quo…

移动端APP测试方法

1 APP测试基本流程 1.1 测试周期 测试周期可按项目的开发周期来确定测试时间&#xff0c;一般测试时间为两三周&#xff08;即15个工作日&#xff09;&#xff0c;根据项目情况以及版本质量可适当缩短或延长测试时间。正式测试前先向主管确认项目排期。 1.2 测试资源 测试任…

冬天来了,波司登的高端化“春天”不远了?

最近&#xff0c;羽绒服频繁“贵”上热搜。 在众多热搜词条中&#xff0c;一条“国产羽绒服卖到7000元”的话题一度将波司登推上了舆论的风口浪尖。 对此&#xff0c;波司登在最新的业绩说明会上进行了回应&#xff0c;公司表示&#xff1a;“波司登旗下主品牌及子品牌将形成差…

mysql原理--InnoDB记录结构

1.InnoDB行格式 我们平时是以记录为单位来向表中插入数据的&#xff0c;这些记录在磁盘上的存放方式也被称为 行格式 或者 记录格式 。 设计 InnoDB 存储引擎的大叔们到现在为止设计了4种不同类型的 行格式 &#xff0c;分别是 Compact 、 Redundant 、Dynamic 和 Compressed 行…

抖音直播间购物小黄车的商品详情数据SKU接口轻松拿下

我们都知道&#xff0c;抖音直播购物车的商品链接只能是抖音小店的&#xff0c;如果没有开通抖音小店&#xff0c;还能添加小店链接吗&#xff1f; 也是可以的。 抖音直播小黄车的链接可以是自己的小店商品&#xff0c;也可以是别人的小店商品。 抖音直播上链接有两种方式&a…

centos7防火墙开启端口

1.查看防火墙状态 firewall-cmd --state如果返回的not running&#xff0c;那么需要先开启防火墙 2.开启关闭防火墙 systemctl start firewalld.service systemctl stop firewalld.service systemctl restart firewalld.service3.开放指定端口 firewall-cmd --zonepublic -…

MYSQL8用户权限配置详解

单位的系统性能问题需要把Mysql5升级到Mysql8&#xff0c;需要用到Mysql8的一些特性来提升系统的性能。 配置用户权限过程中发现一些问题&#xff0c;学习并记录一下。 目录 一、环境 二、MySQL8 用户权限 2.1 账号管理权限 2.1.1 连接数据库 2.1.2 账号权限配置 2.2 密码…