概述
"異步傳染性"問題通常是指,當(dāng)一個函數(shù)使用了async和await,其調(diào)用者也需要使用async和await處理異步操作,導(dǎo)致整個調(diào)用鏈都變成異步的。這種情況可能導(dǎo)致代碼變得更復(fù)雜,不易維護。
類似于C# try catch的層層上拋,在某一層catch
觀點
查了很多資料 ,對于這個問題說法還都不一樣
- async/await異步傳染性無法解決
- 可以解決但開銷不小/ 解決代價不大
無法解決
在node 的層面沒法解決, 除非v8 或者jscore等等提供 GMP 模型+Socket 勾子。
不是該考慮怎么消除async,而是該考慮怎么在需要的時候給任意程序入口加一個異步上下文。除此之外任何想要在程序中段消除async的需求都是偽需求。
可以解決
- 合理分層:將異步操作集中在特定層次,例如數(shù)據(jù)訪問層或API調(diào)用層,這樣可以將異步操作限制在這些層次,而不會傳播到整個代碼庫。在這種架構(gòu)下,其他層次的代碼可以保持同步方式處理數(shù)據(jù),從而降低代碼復(fù)雜度。
- 使用Promise:當(dāng)使用async和await時,實際上底層是基于Promise的。你可以盡量使用Promise鏈式調(diào)用(.then()和.catch()),在某種程度上減少異步傳染性。但請注意,過度使用Promise鏈式調(diào)用可能導(dǎo)致回調(diào)地獄問題。
- 使用事件驅(qū)動:異步傳染性問題有時候是因為代碼邏輯緊密耦合所導(dǎo)致的??紤]使用事件驅(qū)動架構(gòu),通過發(fā)布和訂閱事件的方式來解耦代碼。這樣,即使某個操作是異步的,它也不會影響到其他操作的同步性。
- 限制異步操作的范圍:盡量讓異步操作獨立,不要過多地依賴其他異步操作的結(jié)果。如果確實需要依賴其他異步操作的結(jié)果,嘗試將這些操作分組,并使用Promise.all()或Promise.race()等方法來處理。
- 避免不必要的異步操作:不要將原本可以用同步方式實現(xiàn)的操作變成異步操作。異步操作應(yīng)該只用于真正需要的場景,例如網(wǎng)絡(luò)請求、文件讀寫等。
- ES2021 可以用top-level await
- 封裝異步操作:將需要異步操作的函數(shù)封裝成一個單獨的函數(shù),該函數(shù)內(nèi)部使用 async/await
來處理異步邏輯。然后,在需要調(diào)用這個異步函數(shù)的地方,可以直接調(diào)用它,而不需要在調(diào)用者處添加 async/await。 - 使用異步函數(shù)的返回值:如果調(diào)用異步函數(shù)的結(jié)果在調(diào)用者中不需要立即使用,可以簡單地返回異步函數(shù)的 Promise 對象,而不是在調(diào)用者處添加async/await。然后在需要使用結(jié)果的地方,再使用 async/await 處理。
- 使用回調(diào)函數(shù):如果不適合使用async/await,可以考慮使用回調(diào)函數(shù)的方式處理異步操作。將異步函數(shù)的回調(diào)函數(shù)傳遞給異步函數(shù),在回調(diào)函數(shù)中處理結(jié)果。
以下是一個簡單的示例,展示了如何將異步操作限制在數(shù)據(jù)訪問層,并使用事件驅(qū)動來解耦代碼:
數(shù)據(jù)訪問層(使用異步操作):
// 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}`);
}
};
事件處理器(處理數(shù)據(jù)訪問層的結(jié)果,發(fā)布事件):
// 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);
}
};
主邏輯(訂閱事件,處理事件結(jié)果):
// 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);
// 觸發(fā)數(shù)據(jù)請求
fetchAndEmitData("https://api.example.com/data");
在這個示例中,我們將異步操作限制在了dataAccessLayer.js中。eventHandler.js負責(zé)處理這些異步操作的結(jié)果,并發(fā)布相應(yīng)的事件。main.js則訂閱這些事件并處理它們的結(jié)果。這樣,我們在主邏輯中避免了使用async和await,從而降低了代碼復(fù)雜度。
還有一種解決方案很有意思,是利用異常捕獲達成的,對其可行性表示懷疑
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);
}
從上面的函數(shù)調(diào)用可以看出來,getUser是異步函數(shù),所有使用和相關(guān)聯(lián)的函數(shù)都必須使用async/await變成異步函數(shù),這樣使用也沒有什么問題,但是在函數(shù)式編程環(huán)境中就不合適了。
本來這些函數(shù)應(yīng)該是一個純函數(shù)的,卻因為異步具有傳染性,導(dǎo)致這些函數(shù)全部變成副作用的了,這在函數(shù)式編程環(huán)境中是很難接受的。
所以如何不去改動這些函數(shù),把這些異步全部去掉呢?變成沒有異步的樣子,從而保持這些函數(shù)的純度。如下:
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調(diào)用了fetch請求,導(dǎo)致了異步的產(chǎn)生。
網(wǎng)絡(luò)傳輸是需要時間的,這個是無法改變的。讓瀏覽器完全阻塞那就卡死了,肯定是行不通的。
目前的函數(shù)調(diào)用流程如下:
main->getUser->fetch - -> 等待網(wǎng)絡(luò)請求,請求完成 --> getUser->main
由于fetch需要等待導(dǎo)致所有相關(guān)的函數(shù)都要等待。那么只能在fetch這里做一些操作了。如何讓fetch不等待,就只能報錯了。
我們看下通過fetch報錯如何解決這個問題。
main->getUser->fetch->error
拿到結(jié)果存入cache: main->getUser->fetch->cache->getUser->main
在調(diào)用fetch的時候不等待了而是報錯,這樣所有函數(shù)都終止了,調(diào)用棧層層彈出,調(diào)用結(jié)束。
但是我們最終的目的是要拿到結(jié)果的,前面雖然報錯了,網(wǎng)絡(luò)線程仍然還在繼續(xù)網(wǎng)絡(luò)請求它不會停止,直到拿到結(jié)果。
拿到結(jié)果后我們把它放在一個緩存中,接著再去恢復(fù)整個調(diào)用鏈的執(zhí)行。再執(zhí)行fetch時,結(jié)果已經(jīng)緩存在cache了,取出數(shù)據(jù)就可以直接交付不用等待了從而變成了同步函數(shù)。
整個過程會走兩次,第一次以錯誤結(jié)束,第二次以成功結(jié)束,這兩次都是同步的。
在這個過程中fetch的邏輯就發(fā)生了變化:
fetch時要判斷是否有緩存,如果有緩存則返回緩存,如果沒有緩存則發(fā)送真實請求同時拋出錯誤,然后把請求的結(jié)果保存。拋出的錯誤為發(fā)送請求返回的Promise對象,目的是為了在請求完成后再去恢復(fù)調(diào)用。
偽代碼實現(xiàn)如下:文章來源:http://www.zghlxwxcb.cn/news/detail-818547.html
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. 發(fā)送真是請求
const promise = oldFetch(...args)
.then(res=>{
cache.status = 'fulfilled';
cache.value = res;
}, err=> {
cache.status = 'rejected';
cache.value = err;
});
// 2. 拋出錯誤
throw promise;
}
}
// 執(zhí)行入口函數(shù)
try {
func();
} catch (error) {
if(error instanceof Promise) {
// 不論成功還是失敗都重新調(diào)用
error.then(func,func).finally(()=>{
//恢復(fù)原來的值
window.fetch = oldFetch;
});
}
}
}
run(main);
來源
在前端開發(fā)中如何消除異步的傳染性?
消除async/await異步的傳染性
如何解決 async/await 的傳染性?文章來源地址http://www.zghlxwxcb.cn/news/detail-818547.html
到了這里,關(guān)于【前端】淺談async/await異步傳染性的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!