JavaScript異步編程 Async/Await 使用從原理到最佳實(shí)踐記錄
1.背景與概念
在傳統(tǒng) JavaScript 開發(fā)中,開發(fā)者長(zhǎng)期面臨回調(diào)地獄的困擾。隨著 ES6 Promise 的出現(xiàn),異步代碼的可讀性得到改善,但鏈?zhǔn)秸{(diào)用依然存在嵌套問題。2017 年 ES8 正式引入的 Async/Await 語法,讓異步代碼第一次擁有了同步代碼般的可讀性。

async 函數(shù)是基于 Promise 的語法糖,用于簡(jiǎn)化異步操作的書寫方式。使用 async 聲明的函數(shù)會(huì)隱式返回一個(gè) Promise,函數(shù)體內(nèi)部可以通過 await 暫停執(zhí)行,直到對(duì)應(yīng)的 Promise 完成或拋出錯(cuò)誤后再繼續(xù)執(zhí)行。await 操作符只能在 async 函數(shù)或模塊頂層中使用,用于等待一個(gè) Promise 解決,并將其結(jié)果作為表達(dá)式的值返回;如果 Promise 被拒絕,則會(huì)在該位置拋出異常,可配合常規(guī)的 try...catch 進(jìn)行捕獲處理。
這種寫法極大地提升了異步代碼的可讀性,使得我們可以像編寫同步代碼一樣直觀地處理異步邏輯,同時(shí)保留了后臺(tái)并發(fā)執(zhí)行的優(yōu)勢(shì)。
2. 語法詳解
2.1 聲明與返回值
async function foo() {
return 42;
}上例中, foo() 會(huì)返回一個(gè)已解決(fulfilled)的 Promise,其值為 42;等價(jià)于:
function foo() {
return Promise.resolve(42);
}這是因?yàn)槿魏?async 函數(shù)內(nèi)的返回值都會(huì)被自動(dòng)封裝為 Promise
2.2 使用 await 暫停執(zhí)行
async function fetchData() {
let response = await fetch('/api/data');
let data = await response.json();
return data;
}代碼解釋:
- 第一行的
await fetch(...)會(huì)暫停fetchData的執(zhí)行,直到fetch返回的Promise完成,并將其結(jié)果賦值給 response - 第二行的
await response.json()同理,等待解析JSON后再繼續(xù)執(zhí)行 - 如果任一
Promise拒絕,則會(huì)在該await位置拋出異常,可在外層使用try...catch捕捉。
2.3 錯(cuò)誤處理
async function safeFetch() {
try {
let res = await fetch('/bad/url');
let json = await res.json();
return json;
} catch (err) {
console.error('請(qǐng)求失?。?, err);
throw err; // 可再次拋出或返回默認(rèn)值
}
}上述模式與同步代碼中使用 try…catch 完全一致,大大簡(jiǎn)化了基于 Promise 鏈?zhǔn)?.catch() 的寫法
2.4 語法規(guī)則
下面我們看看我們常用的一些使用語法
// 聲明異步函數(shù)
async function fetchUser() {
return { name: 'Alice', age: 28 }; // 自動(dòng)包裝為Promise
}
// 使用箭頭函數(shù)
const fetchData = async () => {
const res = await fetch('/api/data');
return res.json();
};
// 立即調(diào)用模式
(async () => {
const data = await fetchData();
console.log(data);
})();3. 并發(fā)與性能
3.1 順序等待 vs 并行等待
默認(rèn)情況下,連續(xù)的 await 會(huì)串行執(zhí)行:
let a = await task1(); let b = await task2();
若兩者互不依賴,可改為并行:
let [a, b] = await Promise.all([task1(), task2()]);
3.2 并行執(zhí)行優(yōu)化
通過上面 順序等待 vs 并行等待 的介紹,通常我們可以按照以下形式來進(jìn)行優(yōu)化(模擬請(qǐng)求)
// 順序執(zhí)行(總耗時(shí) = 各請(qǐng)求耗時(shí)之和)
async function serialRequests() {
const res1 = await fetch('/api/1');
const res2 = await fetch('/api/2');
return [await res1.json(), await res2.json()];
}
// 并行執(zhí)行(總耗時(shí) ≈ 最慢請(qǐng)求耗時(shí))
async function parallelRequests() {
const [res1, res2] = await Promise.all([
fetch('/api/1'),
fetch('/api/2')
]);
return await Promise.all([res1.json(), res2.json()]);
}3.2 限制并發(fā)數(shù)量
在需要對(duì)大量異步任務(wù)進(jìn)行限流時(shí),可使用第三方庫(如 p-limit)或自己實(shí)現(xiàn)簡(jiǎn)單隊(duì)列,避免一次性發(fā)起過多請(qǐng)求導(dǎo)致資源競(jìng)爭(zhēng)或網(wǎng)絡(luò)擁堵
4. 異步迭代
ES2018 引入了 for await...of,用于遍歷異步可迭代對(duì)象(如異步生成器):
async function* gen() {
yield await fetchChunk(1);
yield await fetchChunk(2);
}
(async () => {
for await (let chunk of gen()) {
console.log(chunk);
}
})();該語法在處理流式數(shù)據(jù)(例如文件分塊下載)時(shí)非常有用
5 常見問題解決方案
5.1 請(qǐng)求重試機(jī)制
在外面日常開發(fā)中,會(huì)遇到請(qǐng)求失敗需要重試的需求,來看看以下模擬代碼
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
return await res.json();
} catch (err) {
if (i === retries - 1) throw err;
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}5.2 競(jìng)態(tài)條件處理
當(dāng)多個(gè)請(qǐng)求并發(fā)執(zhí)行時(shí),可能因網(wǎng)絡(luò)延遲、服務(wù)器響應(yīng)速度差異等問題導(dǎo)致響應(yīng)順序與發(fā)送順序不一致問題。
更詳細(xì)講解可以查閱博主寫過一篇【 前端請(qǐng)求亂序問題分析與AbortController、async/await、Promise.all等解決方案】
以下僅展現(xiàn)實(shí)現(xiàn)代碼:
let lastController = null;
async function search(query) {
// 取消前一個(gè)未完成的請(qǐng)求
if (lastController) lastController.abort();
const controller = new AbortController();
lastController = controller;
try {
const res = await fetch(`/api/search?q=${query}`, {
signal: controller.signal
});
return await res.json();
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
}5.3 異步生成器
在 ·async function*· 中,你既可以使用 ·await·,也可以使用 ·yield·,將異步任務(wù)與懶加載結(jié)合:
async function* asyncGenerator() {
for (let i = 0; i < 3; i++) {
await delay(1000);
yield i;
}
}這種方式適合按需獲取異步數(shù)據(jù),提高資源利用率
5.4 處理非 Promise 值
await 后可以跟任意表達(dá)式,如果其值不是 Promise,則會(huì)被包裝為立即解決的 Promise。例如:
let x = await 123; // 相當(dāng)于 await Promise.resolve(123)
但建議對(duì)非異步操作避免使用 await,以免誤導(dǎo)
5.5 常見陷阱
- 遺忘 await:調(diào)用 async 函數(shù)但未加 await,會(huì)得到未決(pending)的 Promise 而非預(yù)期結(jié)果
- 在非 async 環(huán)境使用 await:僅在模塊頂層或 async 函數(shù)內(nèi)部可用,否則會(huì)拋語法錯(cuò)誤
- Promise.all 中單個(gè)失敗導(dǎo)致整體失敗:若需要容忍部分失敗,可對(duì)內(nèi)部 Promise 使用 .catch() 處理,避免整體拒絕
- 濫用并發(fā):同時(shí)發(fā)起過多網(wǎng)絡(luò)請(qǐng)求可能觸發(fā)限流或阻塞,建議根據(jù)場(chǎng)景調(diào)整并發(fā)策略
6. 性能優(yōu)化實(shí)踐
博主這里例舉兩個(gè)優(yōu)化的案例:內(nèi)存管理以及優(yōu)先加載優(yōu)化
6.1 內(nèi)存管理
常見一些大量數(shù)據(jù)的獲取下載
async function processLargeData() {
const data = await getHugeData(); // 大數(shù)據(jù)量
// 分塊處理
for (let i = 0; i < data.length; i += 1000) {
const chunk = data.slice(i, i + 1000);
await processChunk(chunk);
data[i] = null; // 及時(shí)釋放內(nèi)存
}
}6.2 優(yōu)先加載優(yōu)化
async function loadCriticalResources() {
// 預(yù)加載非關(guān)鍵資源
const nonCritical = fetch('/non-critical').then(r => r.json());
// 優(yōu)先處理關(guān)鍵資源
const user = await fetchUser();
const config = await fetchConfig();
// 等待非關(guān)鍵資源
const data = await nonCritical;
return { user, config, data };
}7. 結(jié)語
Async/Await 的引入徹底改變了 JavaScript 異步編程的面貌。通過本文的講解,相信小伙伴們可以掌握 Async/Await 的使用精髓,將使您的 JavaScript 代碼在保持高性能的同時(shí),獲得質(zhì)的可讀性和可維護(hù)性提升。
到此這篇關(guān)于JavaScript異步編程 Async/Await 使用詳解:從原理到最佳實(shí)踐的文章就介紹到這了,更多相關(guān)js異步編程Async/Await 使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
wavesurfer.js繪制音頻波形圖的實(shí)現(xiàn)
這篇文章主要介紹了wavesurfer.js繪制音頻波形圖的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
TypeScript利用TS封裝Axios實(shí)戰(zhàn)
這篇文章主要介紹了TypeScript利用TS封裝Axios實(shí)戰(zhàn),TypeScript封裝一遍Axios,能進(jìn)一步鞏固TypeScript的基礎(chǔ)知識(shí),需要的小伙伴可以參考一下2022-06-06
JavaScript限定復(fù)選框的選擇個(gè)數(shù)示例代碼
有10個(gè)復(fù)選框,用戶最多只能勾選3個(gè),否則就灰掉所有復(fù)選框,具體實(shí)現(xiàn)思路及代碼如下,感興趣的朋友可以參考下,希望對(duì)大家有所幫助2013-08-08
javascript iframe中打開文件,并檢測(cè)iframe存在否
從iframe中打開文件,并檢測(cè)iframe存在否如果說只是檢測(cè)頁面存在否,直接設(shè)置target用偽協(xié)議就可以解決了...2008-12-12
原生js實(shí)現(xiàn)簡(jiǎn)易計(jì)算器
這篇文章主要為大家詳細(xì)介紹了原生js實(shí)現(xiàn)簡(jiǎn)易計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
JS/jQuery實(shí)現(xiàn)超簡(jiǎn)單的Table表格添加,刪除行功能示例
這篇文章主要介紹了JS/jQuery實(shí)現(xiàn)超簡(jiǎn)單的Table表格添加,刪除行功能,結(jié)合實(shí)例形式詳細(xì)分析了JS與jQuery針對(duì)Table表格添加,刪除行功能的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-07-07
JavaScript實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-01-01

