【前端】浅谈async/await异步传染性

文章目录

  • 概述
  • 观点
    • 无法解决
    • 可以解决
  • 来源

概述

"异步传染性"问题通常是指,当一个函数使用了async和await,其调用者也需要使用async和await处理异步操作,导致整个调用链都变成异步的。这种情况可能导致代码变得更复杂,不易维护。

类似于C# try catch的层层上抛,在某一层catch

观点

查了很多资料 ,对于这个问题说法还都不一样

  1. async/await异步传染性无法解决
  2. 可以解决但开销不小/ 解决代价不大

无法解决

在node 的層面沒法解決, 除非v8 或者jscore等等提供 GMP 模型+Socket 勾子。

不是该考虑怎么消除async,而是该考虑怎么在需要的时候给任意程序入口加一个异步上下文。除此之外任何想要在程序中段消除async的需求都是伪需求。

可以解决

  • 合理分层:将异步操作集中在特定层次,例如数据访问层或API调用层,这样可以将异步操作限制在这些层次,而不会传播到整个代码库。在这种架构下,其他层次的代码可以保持同步方式处理数据,从而降低代码复杂度。
  • 使用Promise:当使用async和await时,实际上底层是基于Promise的。你可以尽量使用Promise链式调用(.then()和.catch()),在某种程度上减少异步传染性。但请注意,过度使用Promise链式调用可能导致回调地狱问题。
  • 使用事件驱动:异步传染性问题有时候是因为代码逻辑紧密耦合所导致的。考虑使用事件驱动架构,通过发布和订阅事件的方式来解耦代码。这样,即使某个操作是异步的,它也不会影响到其他操作的同步性。
  • 限制异步操作的范围:尽量让异步操作独立,不要过多地依赖其他异步操作的结果。如果确实需要依赖其他异步操作的结果,尝试将这些操作分组,并使用Promise.all()或Promise.race()等方法来处理。
  • 避免不必要的异步操作:不要将原本可以用同步方式实现的操作变成异步操作。异步操作应该只用于真正需要的场景,例如网络请求、文件读写等。
  • ES2021 可以用top-level await
  • 封装异步操作:将需要异步操作的函数封装成一个单独的函数,该函数内部使用 async/await
    来处理异步逻辑。然后,在需要调用这个异步函数的地方,可以直接调用它,而不需要在调用者处添加 async/await。
  • 使用异步函数的返回值:如果调用异步函数的结果在调用者中不需要立即使用,可以简单地返回异步函数的 Promise 对象,而不是在调用者处添加async/await。然后在需要使用结果的地方,再使用 async/await 处理。
  • 使用回调函数:如果不适合使用async/await,可以考虑使用回调函数的方式处理异步操作。将异步函数的回调函数传递给异步函数,在回调函数中处理结果。

以下是一个简单的示例,展示了如何将异步操作限制在数据访问层,并使用事件驱动来解耦代码:

数据访问层(使用异步操作):

// dataAccessLayer.js
import axios from "axios";

export const fetchData = async (url) => {
  try {
    const response = await axios.get(url);
    return response.data;
  } catch (error) {
    throw new Error(`Error fetching data: ${error.message}`);
  }
};

事件处理器(处理数据访问层的结果,发布事件):

// eventHandler.js
import EventEmitter from "events";
import { fetchData } from "./dataAccessLayer";

export const eventEmitter = new EventEmitter();

export const fetchAndEmitData = async (url) => {
  try {
    const data = await fetchData(url);
    eventEmitter.emit("dataFetched", data);
  } catch (error) {
    eventEmitter.emit("dataFetchError", error);
  }
};

主逻辑(订阅事件,处理事件结果):

// main.js
import { fetchAndEmitData, eventEmitter } from "./eventHandler";

const onDataFetched = (data) => {
  console.log("Data fetched successfully:", data);
};

const onDataFetchError = (error) => {
  console.error("Data fetch error:", error.message);
};

// 订阅事件
eventEmitter.on("dataFetched", onDataFetched);
eventEmitter.on("dataFetchError", onDataFetchError);

// 触发数据请求
fetchAndEmitData("https://api.example.com/data");

在这个示例中,我们将异步操作限制在了dataAccessLayer.js中。eventHandler.js负责处理这些异步操作的结果,并发布相应的事件。main.js则订阅这些事件并处理它们的结果。这样,我们在主逻辑中避免了使用async和await,从而降低了代码复杂度。

还有一种解决方案很有意思,是利用异常捕获达成的,对其可行性表示怀疑

async function getUser() {
    return await fetch('./1.json');
}

async function m1() {
    const user = await getUser();
    return user;
}

async function m2() {
    const user = await m1();
    return user;
}
async function m3() {
    const user = await m2();
    return user;
}

async function main() {
    const user = await m3();
    console.log(user);
}

从上面的函数调用可以看出来,getUser是异步函数,所有使用和相关联的函数都必须使用async/await变成异步函数,这样使用也没有什么问题,但是在函数式编程环境中就不合适了。
本来这些函数应该是一个纯函数的,却因为异步具有传染性,导致这些函数全部变成副作用的了,这在函数式编程环境中是很难接受的。

所以如何不去改动这些函数,把这些异步全部去掉呢?变成没有异步的样子,从而保持这些函数的纯度。如下:

 function getUser() {
    return fetch('./1.json');
}

 function m1() {
    const user = getUser();
    return user;
}

 function m2() {
    const user = m1();
    return user;
}
 function m3() {
    const user = m2();
    return user;
}

 function main() {
    const user = m3();
    console.log(user);
}

怎么操作呢?getUser调用了fetch请求,导致了异步的产生。
网络传输是需要时间的,这个是无法改变的。让浏览器完全阻塞那就卡死了,肯定是行不通的。
目前的函数调用流程如下:
在这里插入图片描述
main->getUser->fetch - -> 等待网络请求,请求完成 --> getUser->main

由于fetch需要等待导致所有相关的函数都要等待。那么只能在fetch这里做一些操作了。如何让fetch不等待,就只能报错了。
在这里插入图片描述
我们看下通过fetch报错如何解决这个问题。

main->getUser->fetch->error
拿到结果存入cache: main->getUser->fetch->cache->getUser->main

在调用fetch的时候不等待了而是报错,这样所有函数都终止了,调用栈层层弹出,调用结束。

但是我们最终的目的是要拿到结果的,前面虽然报错了,网络线程仍然还在继续网络请求它不会停止,直到拿到结果。

拿到结果后我们把它放在一个缓存中,接着再去恢复整个调用链的执行。再执行fetch时,结果已经缓存在cache了,取出数据就可以直接交付不用等待了从而变成了同步函数。

整个过程会走两次,第一次以错误结束,第二次以成功结束,这两次都是同步的。

在这个过程中fetch的逻辑就发生了变化:
fetch时要判断是否有缓存,如果有缓存则返回缓存,如果没有缓存则发送真实请求同时抛出错误,然后把请求的结果保存。抛出的错误为发送请求返回的Promise对象,目的是为了在请求完成后再去恢复调用。

伪代码实现如下:

function run(func) {
    let cache = {
        status: 'pending',
        value: null
    };
    const oldFetch = window.fetch;
    window.fetch = function(...args){
        if(cache.status == 'fulfilled'){
            return cache.value;
        }else if(cache.status == 'rejected'){
            //之前的请求有问题
            throw cache.value;
        }else{
            // 1. 发送真是请求
            const promise = oldFetch(...args)
            .then(res=>{
                cache.status = 'fulfilled';
                cache.value = res;
            }, err=> {
                cache.status = 'rejected';
                cache.value = err;
            });
            // 2. 抛出错误
            throw promise;
        }
        
    }
    // 执行入口函数
    try {
        func();
    } catch (error) {
        if(error instanceof Promise) {
            // 不论成功还是失败都重新调用
            error.then(func,func).finally(()=>{
                //恢复原来的值
                window.fetch = oldFetch;
            });
        }
    }
    
}
run(main);

来源

在前端开发中如何消除异步的传染性?
消除async/await异步的传染性
如何解决 async/await 的传染性?

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(三)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 这一节主要是对每个流程节点的字段规则设置与操作规则设置,目前也是只针对自定义业务表单。 1、…

基于SSM的社区团购系统设计与实现

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…

python基于YOLOv8全系列模型【n/s/m/l/x】开发构建不同参数量级的钢铁产业产品智能自动化检测识别系统

在前文的项目开发实践中,我们已经以钢铁产业产品缺陷检测数据场景为基准,陆续开发构建了多款目标检测模型,感兴趣的话可以自行阅读即可。 《YOLOv3老矣尚能战否?基于YOLOv3开发构建建钢铁产业产品智能自动化检测识别系统&#xf…

智能优化算法应用:基于人工蜂群算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于人工蜂群算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于人工蜂群算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.人工蜂群算法4.实验参数设定5.算法结果6.参考…

中伟视界:AI智能分析盒子的人数统计AI算法通过什么算法模型可以解决重复统计的问题?

在AI盒子的人数统计中,常常会遇到重复统计的问题。当有多人同时出入视野范围时,传统的算法模型很难准确识别和计算人数,容易导致重复统计。为解决这一难题,AI算法模型可以采用多种方法来提高准确性并避免重复统计。 一种常用的解决…

【沐风老师】3DMAX拼图建模工具MaxPuzzle2D插件使用方法详解

MaxPuzzle2D拼图建模工具使用帮助 MaxPuzzle2D拼图建模工具,拼图建模“彩虹系列”插件,是一款用MAXScript脚本语言开发的3dMax拼图建模小工具,可以创建2D或3D的拼图图形阵列。这让需要拼图建模的设计师大大节省了时间。 MaxPuzzle2D工具界面&…

移动应用开发介绍及iOS方向学习路线(HUT移动组版)

移动应用开发介绍及iOS方向学习路线(HUT移动组版) 前言 ​ 作为一个HUT移动组待了一坤年(两年半)多的老人,在这里为还在考虑进哪个组的萌新们以及将来进组的新朋友提供一份关于移动应用开发介绍以及学习路线的白话文…

中间件安全:JBoss 反序列化命令执行漏洞.(CVE-2017-12149)

中间件安全:JBoss 反序列化命令执行漏洞.(CVE-2017-12149) JBoss 反序列化漏洞,该漏洞位于 JBoss 的 HttpInvoker 组件中的 ReadOnlyAccessFilter 过滤器中,其 doFilter 方法在没有进行任何安全检查和限制的情况下尝试…

神奇植物在哪里?文心大模型助力一秒读懂花草的“前世今生”

本期文心开发者说邀请到飞桨开发者技术专家谢杰航老师,分享如何利用AI技术构建风景园林行业的植物知识科普系统,接着还介绍了大模型应用的基本技术流程框架,多模态特征提取以及使用向量数据库的优势,使用飞桨星河社区运行向量数据…

SAP VL01N Error VL 367

在VL01N 创建交货单的时候,报错:An item with no delivery quantity is not permitted. Item will be deleted. 第一种情况: 1.首先MMBEcheck 可用库存,非限制使用库存是否充足 注意:这里框出来的交货库存也是非限制…

Python超级详细的变量命名规则

Python 需要使用标识符给变量命名,其实标识符就是用于给程序中变量、类、方法命名的符号(简单来说,标识符就是合法的名字)。 Python 语言的标识符必须以字母、下画线(_)开头,后面可以跟任意数目…

GitLab 登录中,LDAP和 Standard 验证有什么区别

在 GitLab 中,LDAP(Lightweight Directory Access Protocol)和 Standard 验证是两种不同的身份验证方法,它们有以下区别: LDAP(Lightweight Directory Access Protocol)身份验证: L…

Kafka事务机制:原理和实践

Kafka事务机制:原理和实践 Apache Kafka 是一个分布式流处理平台,广泛用于构建实时数据管道和流应用程序。它不仅以高吞吐量、可扩展性和容错能力著称,还提供了事务支持,以确保数据的完整性和一致性。在这篇博客中,我…

Android flutter项目 启动优化实战(一)使用benchmark分析项目

背景描述 启动时间是用户对应用的第一印象,较慢的加载会对用户的留存和互动造成负面影响 在刚上线的B端项目中: 1.提高启动速度能提高整体流程的效率 2.提高首次运行速度能提高应用推广的初体验效果 问题描述 项目刚上线没多久、目前存在冷启动过程存在…

C#:程序发布的大小控制

.net不讨喜有个大原因就是.net平台本身太大了,不同版本没有兼容性,程序依赖哪个版本用户就要安装哪个版本,除非你恰好用的是操作系统默认安装的版本——问题是不同版本操作系统默认安装的不一样。 所以打包程序就很头疼,不打包平台…

设备管理的方法与思路

阅读本文你将了解设备管理的思路与方法:一、制定全面的管理计划;二、标准化管理流程;三、设备维护与保养;四、风险管理与预防;五、引入数字化工具。 设备管理在生产制造领域是保障生产安全和效率的核心环节。通过引入…

Android Studio 模拟器设置独立窗口

目录 模拟器在窗口内部运行 设置成独立窗口 模拟器在窗口内部运行 操作起来十分不便 设置成独立窗口 Android Studio -> Preferences(Settings) -> Tools-> Emulator ->取消勾选 Launch in a tool window -> 点击右下角的 OK 按钮 -> 重启 Android Studio

初次尝到【C知道】的甜蜜

目录 一、场景描述 二、【C知道】使用 三、【C知道】的原理 四、【C知道】的坑 一、场景描述 最近有下面的需求: mysql如何通过命令查看指定表的存储引擎 习惯性在CSDN中搜文章,自己找。 皇天不负有心人,我找到了下面这个内容&#xff0…

食谱菜谱大全API接口

食谱菜谱大全API接口 一、食谱菜谱大全API接口二、使用步骤1、接口2、请求参数3、请求参数示例4、接口 返回示例 三、 如何获取appKey和uid1、申请appKey:2、获取appKey和uid 四、重要说明 一、食谱菜谱大全API接口 包含所有家用或者商用的食谱菜谱的API接口 二、使用步骤 1…

面试篇spark(spark core,spark sql,spark 优化)

一:为什么学习spark? 相比较map-reduce框架,spark的框架执行效率更加高效。 mapreduce的执行框架示意图。 spark执行框架示意图 spark的执行中间结果是存储在内存当中的,而hdfs的执行中间结果是存储在hdfs中的。所以在运算的时…
最新文章