JavaScript異步編程之Async/Await用法完全指南
一、async/await 的發(fā)展史:為什么會(huì)出現(xiàn)?
要理解 async/await,得先回顧它的 “前輩” 們 ——async/await 不是憑空出現(xiàn)的,而是為了解決 Promise 的 “小痛點(diǎn)”,是 JS 異步編程的終極語法糖。
1. 異步編程的 “進(jìn)化史”(鋪墊)
JS 異步編程的發(fā)展,核心是 “讓異步代碼更像同步代碼”,一步步解決 “寫起來別扭” 的問題:
- 階段 1:回調(diào)函數(shù)(ES5 及以前):解決了異步執(zhí)行的問題,但多層嵌套形成 “回調(diào)地獄”,代碼臃腫;
- 階段 2:Promise(ES2015/ES6,2015 年):解決了回調(diào)地獄,把嵌套改成鏈?zhǔn)秸{(diào)用,但鏈?zhǔn)秸{(diào)用多了(比如 5-6 個(gè)異步操作),then依然會(huì)串聯(lián)成 “長(zhǎng)鏈條”,邏輯不夠直觀;
- 階段 3:Generator 函數(shù)(ES2015/ES6,過渡方案):可以暫停 / 恢復(fù)函數(shù)執(zhí)行,能模擬 “同步寫異步”,但語法繁瑣(需要手動(dòng)調(diào)用
next()),還需要第三方庫(kù)(如 co)封裝,普通人難用; - 階段 4:async/await(ES2017/ES8,2017 年):基于
Promise和Generator,是官方推出的 “語法糖”—— 既保留了 Promise 的異步特性,又能**像寫同步代碼一樣寫異步邏輯**,成為現(xiàn)在異步編程的主流。
2. 各階段具體代碼示例
階段一:回調(diào)函數(shù)時(shí)代(Callback Hell)
getData(function(data1) {
process(data1, function(data2) {
save(data2, function(data3) {
display(data3, function() {
// 更多嵌套...
console.log("完成了,但代碼已經(jīng)看不懂了!");
});
});
});
});
問題:代碼橫向發(fā)展,形成"金字塔",難以維護(hù)和調(diào)試。
階段二:Promise時(shí)代(ES6,2015年)
getData()
.then(process)
.then(save)
.then(display)
.then(() => console.log("完成"))
.catch(error => console.error("出錯(cuò):", error));
改進(jìn):鏈?zhǔn)秸{(diào)用,縱向發(fā)展,錯(cuò)誤處理統(tǒng)一。
階段三:Generator函數(shù)時(shí)代(過渡方案)
// 2015年:Generator + Promise
function* asyncGenerator() {
const data1 = yield getData();
const data2 = yield process(data1);
const data3 = yield save(data2);
yield display(data3);
console.log("完成");
}
// 需要外部執(zhí)行器
function run(generator) {
const iterator = generator();
function handle(result) {
if (result.done) return;
result.value.then(data => {
handle(iterator.next(data));
});
}
handle(iterator.next());
}
問題:需要外部執(zhí)行器,語法復(fù)雜,不直觀。
階段四:Async/Await時(shí)代(ES8,2017年)
// 2017年:Async/Await
async function handleData() {
try {
const data1 = await getData();
const data2 = await process(data1);
const data3 = await save(data2);
await display(data3);
console.log("完成");
} catch (error) {
console.error("出錯(cuò):", error);
}
}
革命性改進(jìn):代碼看起來像同步代碼,但實(shí)際上是異步執(zhí)行!
3. 核心本質(zhì)
async/await 不是替代 Promise,而是基于 Promise 的語法糖—— 所有 async/await 能實(shí)現(xiàn)的功能,用 Promise 都能實(shí)現(xiàn),只是 async/await 寫起來更簡(jiǎn)單、更易讀。
二、async/await 的核心用法
1. 基本語法:async + await
(1)async:聲明 “異步函數(shù)”
- 用
async修飾的函數(shù),會(huì)變成 “異步函數(shù)”; - 異步函數(shù)的返回值會(huì)自動(dòng)包裝成 Promise(哪怕你返回的是普通值)。
// 在函數(shù)前加上 async,這個(gè)函數(shù)就變成了異步函數(shù)
async function normalFunction() {
return "Hello Async"; // 自動(dòng)包裝成Promise
}
// 等價(jià)于:
function normalFunction() {
return Promise.resolve("Hello Async");
}
// 使用
normalFunction().then(console.log); // "Hello Async"
(2)await:等待 Promise 完成
await只能用在async 函數(shù)內(nèi)部(這是新手最容易踩的坑);- await后面必須跟一個(gè)Promise 對(duì)象(如果不是,會(huì)直接返回該值);
- await會(huì) “暫停” 函數(shù)執(zhí)行,直到 Promise 完成(成功 / 失?。?,再繼續(xù)執(zhí)行后面的代碼 —— 但這個(gè) “暫停” 是非阻塞的(不會(huì)卡主線程)。
async function fetchUser() {
// await 會(huì)暫停函數(shù)執(zhí)行,等待Promise完成
const response = await fetch('https://api.example.com/user');
const user = await response.json();
return user;
}
// await 只能在 async 函數(shù)內(nèi)部使用
// 下面的代碼會(huì)報(bào)錯(cuò):
// const data = await fetchUser(); // ? 錯(cuò)誤
?? 對(duì)比 Promise:原來的.then鏈?zhǔn)秸{(diào)用,現(xiàn)在直接用await拿到結(jié)果,代碼和同步邏輯完全一致!
(3)基本用法示例
// 模擬異步函數(shù)
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function example() {
console.log("開始");
await delay(1000); // 等待1秒
console.log("1秒后");
await delay(2000); // 再等待2秒
console.log("再過2秒后");
return "任務(wù)完成";
}
// 調(diào)用
example().then(result => console.log(result));
// 輸出:
// 開始
// 1秒后
// 再過2秒后
// 任務(wù)完成
2. 錯(cuò)誤處理:try/catch(核心)
Promise 用.catch處理錯(cuò)誤,async/await 用try/catch捕獲錯(cuò)誤(因?yàn)?await 會(huì) “解包” Promise 的失敗狀態(tài),必須手動(dòng)捕獲)。
示例 1:捕獲單個(gè)異步操作的錯(cuò)誤
async function getScoreAsync() {
try {
// 成功:執(zhí)行try里的代碼,失?。禾D(zhuǎn)到catch
const score = await getScore();
console.log("分?jǐn)?shù):", score);
} catch (error) {
// 捕獲Promise的reject錯(cuò)誤
console.log("出錯(cuò)了:", error);
}
}
// 測(cè)試:如果getScore返回reject(比如分?jǐn)?shù)50),會(huì)執(zhí)行catch
getScoreAsync();
示例 2:捕獲多個(gè)異步操作的錯(cuò)誤
如果有多個(gè) await,一個(gè) try/catch 可以捕獲所有錯(cuò)誤:
// 模擬兩個(gè)異步請(qǐng)求
function getUser() {
return new Promise((resolve) => setTimeout(() => resolve({ id: 1 }), 500));
}
function getCourse(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
userId === 1 ? resolve({ name: "語文" }) : reject("用戶不存在");
}, 500);
});
}
// 用async/await調(diào)用,統(tǒng)一捕獲錯(cuò)誤
async function getInfo() {
try {
const user = await getUser(); // 第一個(gè)異步操作
const course = await getCourse(user.id); // 第二個(gè)異步操作
console.log("用戶課程:", course); // 輸出:{ name: "語文" }
} catch (error) {
console.log("錯(cuò)誤:", error); // 任何一個(gè)異步失敗,都會(huì)走到這里
}
}
getInfo();
3. 實(shí)戰(zhàn)案例:替代 Promise 鏈?zhǔn)秸{(diào)用(解決回調(diào)地獄)
回顧之前 “登錄→查課程→查成績(jī)” 的案例,用 async/await 改寫后,代碼更簡(jiǎn)潔:
// 封裝3個(gè)異步請(qǐng)求(返回Promise)
function login() {
return new Promise(resolve => setTimeout(() => resolve({ id: 1 }), 500));
}
function getCourse(userId) {
return new Promise(resolve => setTimeout(() => resolve({ id: 10 }), 500));
}
function getScore(courseId) {
return new Promise(resolve => setTimeout(() => resolve({ score: 90 }), 500));
}
// async/await版本:同步寫法,異步執(zhí)行
async function getStudentInfo() {
try {
const user = await login(); // 登錄
const course = await getCourse(user.id); // 查課程
const score = await getScore(course.id); // 查成績(jī)
console.log("最終成績(jī):", score); // 輸出:{ score: 90 }
} catch (error) {
console.log("出錯(cuò)了:", error);
}
}
getStudentInfo();
對(duì)比 Promise 鏈?zhǔn)秸{(diào)用:沒有任何.then,代碼和同步邏輯完全一致,新手也能一眼看懂執(zhí)行順序。
4. 進(jìn)階用法:并發(fā)執(zhí)行(避免不必要的等待)
新手容易犯的錯(cuò):用 await 串行執(zhí)行所有異步操作,導(dǎo)致耗時(shí)過長(zhǎng)。比如:
// 錯(cuò)誤示例:串行執(zhí)行(總耗時(shí)=1s+1s=2s)
async function badDemo() {
const res1 = await new Promise(resolve => setTimeout(() => resolve(1), 1000));
const res2 = await new Promise(resolve => setTimeout(() => resolve(2), 1000));
console.log(res1, res2); // // 總時(shí)間 = 2個(gè)請(qǐng)求時(shí)間之和=2s
}
如果兩個(gè)異步操作互不依賴(比如同時(shí)查兩個(gè)獨(dú)立的接口),應(yīng)該用Promise.all實(shí)現(xiàn)并發(fā):
// 正確示例:并發(fā)執(zhí)行(總耗時(shí)=1s)
async function goodDemo() {
// 1. 先創(chuàng)建兩個(gè)Promise對(duì)象(不要先await)
const promise1 = new Promise(resolve => setTimeout(() => resolve(1), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve(2), 1000));
// 2. 用Promise.all并發(fā)執(zhí)行,await等待所有完成
const [res1, res2] = await Promise.all([promise1, promise2]);
console.log(res1, res2); // 總耗時(shí)1秒
}
?? 核心原則:
- 異步操作有依賴(比如查成績(jī)依賴課程 ID):用
await串行執(zhí)行; - 異步操作無依賴:用
Promise.all+await并發(fā)執(zhí)行,提升效率。
5. 循環(huán)中的 await:避免踩坑
如果在循環(huán)中用 await,默認(rèn)是串行執(zhí)行,比如:
// 串行執(zhí)行:總耗時(shí)3秒(1+1+1)
async function loopDemo() {
const list = [1, 2, 3];
for (const item of list) {
await new Promise(resolve => setTimeout(() => {
console.log(item);
resolve();
}, 1000));
}
}
注意:forEach 中的 async/await 不會(huì)按預(yù)期工作(? 錯(cuò)誤)
如果想讓循環(huán)中的異步操作并發(fā)執(zhí)行,先收集所有 Promise,再用Promise.all:
// map + Promise.all(并行) 并發(fā)執(zhí)行:總耗時(shí)1秒
async function loopDemo() {
const list = [1, 2, 3];
// 1. 收集所有Promise
const promises = list.map(item =>
new Promise(resolve => setTimeout(() => {
console.log(item);
resolve();
}, 1000))
);
// 2. 并發(fā)執(zhí)行
await Promise.all(promises);
}
三、async/await 的常見坑與注意事項(xiàng)
- await 只能在 async 函數(shù)中使用:如果在普通函數(shù)里用 await,會(huì)直接報(bào)錯(cuò)(比如在全局作用域、普通 for 循環(huán)回調(diào)里);
? 解決:要么把函數(shù)改成 async,要么用 IIFE(立即執(zhí)行函數(shù))包裹:
// 全局作用域使用await(ES模塊環(huán)境)
(async () => {
const score = await getScore();
console.log(score);
})();
不要濫用 await:所有異步操作都用 await 串行執(zhí)行,會(huì)導(dǎo)致性能極低(比如 10 個(gè)獨(dú)立接口,本可以 1 秒完成,結(jié)果用了 10 秒);
? 解決:無依賴的異步操作,用Promise.all并發(fā)執(zhí)行。錯(cuò)誤捕獲要完整:如果漏寫 try/catch,await 的 Promise 失敗時(shí),會(huì)拋出 “未捕獲的錯(cuò)誤”,導(dǎo)致程序崩潰;
? 解決:要么用 try/catch,要么在 await 后面加.catch:// 單個(gè)異步操作的簡(jiǎn)化錯(cuò)誤捕獲 const score = await getScore().catch(error => console.log(error));
四、總結(jié):核心要點(diǎn)回顧
發(fā)展史:async/await 是 ES2017(2017)納入標(biāo)準(zhǔn)的語法糖,基于 Promise,解決了 Promise 鏈?zhǔn)秸{(diào)用的繁瑣,讓異步代碼像同步代碼;
核心語法:
- async修飾函數(shù),使其返回 Promise;
- await只能在 async 函數(shù)內(nèi)使用,等待 Promise 完成并返回結(jié)果;
- 錯(cuò)誤處理用try/catch(替代 Promise 的.catch)。
使用原則:
- 有依賴的異步操作:await 串行執(zhí)行;
- 無依賴的異步操作:Promise.all+await 并發(fā)執(zhí)行;
- 必加錯(cuò)誤捕獲,避免程序崩潰。
async/await 是目前 JS 異步編程的 “最優(yōu)解”,掌握它后,不管是前端調(diào)接口、Node.js 寫后端,處理異步邏輯都會(huì)變得極其簡(jiǎn)單~
到此這篇關(guān)于JavaScript異步編程之Async/Await用法的文章就介紹到這了,更多相關(guān)JS Async/Await用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解微信小程序動(dòng)畫Animation執(zhí)行過程
這篇文章主要介紹了微信小程序動(dòng)畫Animation執(zhí)行過程 / 實(shí)現(xiàn)過程 / 實(shí)現(xiàn)方式,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Layui組件Table綁定行點(diǎn)擊事件和獲取行數(shù)據(jù)的方法
今天小編就為大家分享一篇Layui組件Table綁定行點(diǎn)擊事件和獲取行數(shù)據(jù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08
p5.js碼繪“跳動(dòng)的小正方形”的實(shí)現(xiàn)代碼
這篇文章主要介紹了p5.js碼繪“跳動(dòng)的小正方形”,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
Javascript設(shè)計(jì)模式之裝飾者模式詳解篇
本文主要介紹了Javascript設(shè)計(jì)模式之裝飾者模式的相關(guān)知識(shí)。具有一定的參考價(jià)值,下面跟著小編一起來看下吧2017-01-01
解決js相同的正則多次調(diào)用test()返回的值卻不同的問題
今天小編就為大家分享一篇解決js相同的正則多次調(diào)用test()返回的值卻不同的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-10-10
JavaScript中setTimeout使用重要的注意事項(xiàng)總結(jié)
setTimeout用于延遲執(zhí)行函數(shù),異步特性使其不阻塞代碼,這篇文章主要介紹了JavaScript中setTimeout使用注意事項(xiàng)的相關(guān)資料,需注意作用域綁定、參數(shù)傳遞、取消定時(shí)器及精確度問題,需要的朋友可以參考下2025-05-05

