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

news2024/11/26 18:38:29

使用 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.coloradmin.cn/o/1287240.html

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

相关文章

精准测试:提升测试流程的效率与质量

在软件开发的过程中&#xff0c;测试是确保软件质量的关键步骤之一。然而&#xff0c;传统的测试方法往往依赖于测试人员的经验和直觉&#xff0c;效率和准确性存在一定的局限性。为了解决这一问题&#xff0c;精准测试应运而生。精准测试是一种基于数据驱动的测试方法&#xf…

机器学习---使用 EM 算法来进行高斯混合模型的聚类

1. 指定k个高斯分布參数 导包 import math import copy import numpy as np import matplotlib.pyplot as pltisdebug False 全局变量 isdebug可以用来控制是否打印调试信息。当 isdebug 为 True 时&#xff0c;代码中的一些调试信 息将被打印出来&#xff0c;方便进行调试…

执法记录仪、一体化布控球等目前支持的AI智能算法、视频智能分析算法有哪些

一、前端设备实现AI算法 主要是基于安卓的布控球实现&#xff0c;已有的算法包括&#xff1a; 1&#xff09;人脸&#xff1b;2&#xff09;车牌&#xff1b;3&#xff09;是否佩戴安全帽&#xff1b;4&#xff09;是否穿着工装&#xff1b; 可以支持定制开发 烟雾&#xf…

用友NC JiuQiClientReqDispatch反序列化RCE漏洞复现

0x01 产品简介 用友NC是一款企业级ERP软件。作为一种信息化管理工具,用友NC提供了一系列业务管理模块,包括财务会计、采购管理、销售管理、物料管理、生产计划和人力资源管理等,帮助企业实现数字化转型和高效管理。 0x02 漏洞概述 用友 NC JiuQiClientReqDispatch 接口存在…

File类—递归文件搜索执行脚本文件

文章目录 一、需求分析二、File类2.1 File对象的创建2.2 File判断和获取方法2.3 创建和删除方法2.4 遍历文件夹方法 三、Runtime类—常见api四、递归文件搜索执行脚本文件 一、需求分析 在本篇博客中&#xff0c;我们想通过递归文件的方式&#xff0c;在D:\\判断下搜索QQ.exe这…

老师怎样夸学生

老师夸学生可以从以下几个方面入手&#xff1a; 1. 表扬学生的思维深度和独立思考能力。如果学生在文章中有独特的思考角度和深度的思考&#xff0c;老师可以直接点出来赞扬。 2. 赞美学生的语言表达。如果学生的文章用词精准、文笔流畅&#xff0c;老师可以夸奖学生的语言表达…

【人体解剖学与组织胚胎学】练习一高度相联知识点整理及对应习题

文章目录 [toc]骨性鼻旁窦填空题问答题 关节填空题简答题 胸廓填空题简答题![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/827e7d1db3af42858d8734bb81911fea.jpeg)补充 骨性鼻旁窦 填空题 问答题 关节 填空题 简答题 胸廓 填空题 简答题 补充 第二肋对应胸骨…

ChatGPT哪些行业需要学习?

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

7天快速学习计算机基础必考八股文day01:计算机网络

day01计算机网络目录一览图 TCP、UDP协议分别属于什么层——OSI七层模型详解请简述HTTP1.0、1.1、2.0的主要区别——HTTP版本详解请简述常见HTTP状态码及含义——HTTP报文详解请简述对称加密、非对称加密的异同——安全传输的基础请简述HTTPS加密认证的过程——TLS技术详解请简…

9.Unity搭建HTTP服务器

搭建HTTP服务器的几种方式 //1.使用别人做好的HTTP服务器软件&#xff0c;一般作为资源服务器时使用该方式&#xff08;学习阶段建议使用&#xff09; //2.自己编写HTTP服务器应用程序&#xff0c;一般作为Web服务器 或者 短链接游戏服务器时 使用该方式 使用别人做好的HTTP服…

JDK8升级11常见问题

JDK8升级11常见问题 1. 使用rt.jar/jce.jar情况 原代码&#xff1a; <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><targe…

P=NP?

背景&#xff1a;   2000年5月24日&#xff0c;新罕布什尔州的克莱数学研究所列出了数学和计算机科学中七个未解决的问题。然而&#xff0c;直到今天&#xff0c;这些问题中只有一个被解决了&#xff0c;那就是庞加莱猜想&#xff08;Poincar Conjecture&#xff09;——被俄…

Linux下的java环境搭建

1&#xff0c;安装jdk 上传linux使用的jdk到/opt目录下 解压tar -zxvf文件 配置环境变量 vim /etc/profile 在文件中添加 export JAVA_HOME/opt/jdk8 export PATH$PATH:$JAVA_HOME/bin 使文件生效 source /etc/profile 2,安装tomcat 将tomcat包解压&#xff0c;进入bi…

Vue 官方周报 #122 - 如何使用Head插件

Hi &#x1f44b; 本周的问题中&#xff0c;您将学习在Vue中如何使用Head插件。 unhead是一个与框架无关的文档头管理器&#xff0c;您可以使用它来管理页面元数据&#xff0c;如 Vue应用程序中的标题。 它用于Nuxt核心&#xff0c;是UnJS生态系统的一部分。 安装 首先&…

rancher harvester deploy demo 【部署 harvester v1.2.1】

简介 Harvester 是一个现代的、开放的、可互操作的、基于Kubernetes的超融合基础设施(HCI)解决方案。它是一种开源替代方案&#xff0c;专为寻求云原生HCI解决方案的运营商而设计。Harvester运行在裸机服务器上&#xff0c;提供集成的虚拟化和分布式存储功能。除了传统的虚拟机…

Git and solve the problem denied to xx

创建仓库 配置Git git config user.name git config user.email git config MINGW64 /e/GithubCode $ git config --global user.name "name"MINGW64 /e/GithubCode $ git config --global user.email "mailxx.com" 生产ssh ssh-keygen -t rsa -C “xx…

了解应用层的HTTP协议与HTTPS协议,在常规请求的应用中Get与Post的区别

一、HTTP协议 1、http协议的特性2、http协议的请求 请求行 GET请求POST 请求(人脸识别方案)两个请求的区别本质区别&#xff1a; &#xff08;1&#xff09;url 携带的参数是否可见&#xff1a;&#xff08;2&#xff09;参数传递方式&#xff08;3&#xff09;缓存性&#xf…

PWM控制器电路D9741,定时闩锁、短路保护电路,输出基准电压(2.5V) 采用SOP16封装形式

D9741是一块脉宽调制方三用于也收路像机和笔记本电的等设备上的直流转换器。在便携式的仪器设备上。 主要特点&#xff1a;● 高精度基准电路 ● 定时闩锁、短路保护电路 ● 低电压输入时误操作保护电路 ● 输出基准电…

运维知识点-PostgreSql

PostgreSql 下载安装地址安装组件数据目录设置superuser密码 端口安装语言安装完成&#xff0c;是否安装Stack Builder 下载 https://www.postgresql.org/download/windows/ https://get.enterprisedb.com/postgresql/postgresql-13.7-1-windows-x64.exe 我下载的 13.7 安装…

揭秘接口测试的必备基础知识!

这一篇讲接口测试的基础&#xff0c;如果你还在做手工测试&#xff0c;你可以从这里开始入门&#xff0c;做接口测试是最容易的一种自动化测试。 一、接口测试是什么 首先要理解接口测试就是测接口&#xff0c;如图所示&#xff1a; 让我们以数据驱动的视角来看接口测试&#…