【前端】浅谈async/await异步传染性
后台-插件-广告管理-内容页头部广告(手机) |
文章目录
- 概述
- 观点
- 无法解决
- 可以解决
- 来源
概述
"异步传染性"问题通常是指,当一个函数使用了async和await,其调用者也需要使用async和await处理异步操作,导致整个调用链都变成异步的。这种情况可能导致代码变得更复杂,不易维护。
类似于C# try catch的层层上抛,在某一层catch
观点
查了很多资料 ,对于这个问题说法还都不一样
- async/await异步传染性无法解决
- 可以解决但开销不小/ 解决代价不大
无法解决
在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}`); } };- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
事件处理器(处理数据访问层的结果,发布事件):
// 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); } };- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
主逻辑(订阅事件,处理事件结果):
// 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");- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
在这个示例中,我们将异步操作限制在了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); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
从上面的函数调用可以看出来,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); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
怎么操作呢?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);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
来源
在前端开发中如何消除异步的传染性?
消除async/await异步的传染性
如何解决 async/await 的传染性?
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。
在线投稿:投稿 站长QQ:1888636
后台-插件-广告管理-内容页尾部广告(手机) |