JavaScript?async/await的使用場景與規(guī)范
JavaScript異步編程指南:async/await的使用場景與規(guī)范
一、async/await是什么?為什么它是異步編程的救星?
1. 核心概念
async
和await
是ES2017推出的異步編程語法糖,本質是Promise的語法簡化版:
async
修飾的函數(shù)會隱式返回Promise,相當于自動給返回值套上Promise.resolve()
await
只能在async
函數(shù)內使用,能讓代碼"暫停"等待Promise結果,使異步代碼擁有同步寫法的直觀性
2. 對比傳統(tǒng)異步方案
回調函數(shù):
fs.readFile('data.txt', (err, data) => { if (err) throw err; console.log(data); });
Promise鏈式調用:
fetch('api/data') .then(res => res.json()) .then(data => console.log(data)) .catch(err => console.error(err));
async/await:
async function getData() { try { const res = await fetch('api/data'); const data = await res.json(); console.log(data); } catch (err) { console.error(err); } }
優(yōu)勢:代碼結構更接近同步邏輯,避免回調地獄和Promise多層嵌套,錯誤處理更統(tǒng)一。
二、核心用法詳解
1. async函數(shù)基礎語法
// 定義async函數(shù),返回Promise async function getUser() { return { id: 1, name: "張三" }; } // 等價于 function getUser() { return Promise.resolve({ id: 1, name: "張三" }); } // 調用方式 getUser().then(user => { console.log(user.name); // 輸出:張三 });
2. await關鍵字的正確姿勢
async function showDelayMessage() { // 封裝延遲Promise const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); console.log("開始執(zhí)行"); await delay(2000); // 代碼在此暫停2秒 console.log("2秒后執(zhí)行"); const message = await new Promise(resolve => { resolve("延遲消息"); }); console.log(message); // 輸出:延遲消息 } showDelayMessage();
3. 錯誤處理最佳實踐
async function fetchData(url) { try { // 等待請求響應 const response = await fetch(url); // 檢查響應狀態(tài) if (!response.ok) { throw new Error(`請求失敗: ${response.status}`); } // 等待數(shù)據(jù)解析 const data = await response.json(); return data; } catch (error) { console.error("數(shù)據(jù)獲取失敗:", error); throw error; // 可選:向上拋出錯誤 } }
三、六大典型使用場景與實戰(zhàn)案例
1. 場景一:網(wǎng)絡請求(最常用場景)
需求:獲取用戶信息后再獲取該用戶的帖子
async function getUserAndPosts(userId) { try { // 順序執(zhí)行異步操作 const userResponse = await fetch(`/api/users/${userId}`); const user = await userResponse.json(); const postsResponse = await fetch(`/api/posts?userId=${user.id}`); const posts = await postsResponse.json(); return { user, posts }; } catch (error) { console.error("請求失敗:", error); } }
2. 場景二:并行請求優(yōu)化
需求:同時獲取用戶、帖子、評論數(shù)據(jù)
async function fetchAllData() { try { // 并行發(fā)起多個請求 const userPromise = fetch("/api/user/1").then(res => res.json()); const postsPromise = fetch("/api/posts").then(res => res.json()); const commentsPromise = fetch("/api/comments").then(res => res.json()); // 等待所有請求完成 const [user, posts, comments] = await Promise.all([ userPromise, postsPromise, commentsPromise ]); return { user, posts, comments }; } catch (error) { console.error("任一請求失敗:", error); } }
3. 場景三:定時器延遲控制
需求:實現(xiàn)一個可復用的延遲函數(shù)
// 封裝延遲Promise const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); async function taskWithDelay() { console.log("任務開始"); // 等待3秒 await delay(3000); console.log("3秒后執(zhí)行"); // 再等待1秒 await delay(1000); console.log("4秒后執(zhí)行"); }
4. 場景四:文件系統(tǒng)操作(Node.js)
需求:讀取配置文件并解析
const fs = require('fs').promises; // Node.js內置Promise化API async function loadConfig() { try { // 讀取文件內容 const content = await fs.readFile('./config.json', 'utf-8'); // 解析JSON const config = JSON.parse(content); return config; } catch (error) { console.error("配置加載失敗:", error); throw new Error("配置文件異常,請檢查路徑"); } }
5. 場景五:表單提交與驗證
需求:前端表單提交前驗證并發(fā)送請求
async function handleFormSubmit(formData) { // 前端驗證 if (!formData.username || !formData.password) { throw new Error("用戶名和密碼不能為空"); } try { // 發(fā)送提交請求 const response = await fetch('/api/login', { method: 'POST', body: JSON.stringify(formData), headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { return "登錄成功"; } else { throw new Error(result.message || "登錄失敗"); } } catch (error) { console.error("提交失敗:", error); throw error; } }
6. 場景六:處理流數(shù)據(jù)(Node.js)
需求:讀取大文件并逐行處理
const fs = require('fs'); const readline = require('readline'); async function processLargeFile(filePath) { try { const fileStream = fs.createReadStream(filePath); const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity }); let lineCount = 0; // 逐行處理 for await (const line of rl) { lineCount++; // 處理每行數(shù)據(jù) console.log(`行${lineCount}: ${line}`); } console.log(`處理完成,共${lineCount}行`); } catch (error) { console.error("文件處理失敗:", error); } }
四、企業(yè)級代碼規(guī)范與最佳實踐
1. 命名與定義規(guī)范
- 函數(shù)命名:async函數(shù)建議添加
Async
后綴(可選),如fetchUserAsync()
- 定義方式:優(yōu)先使用普通函數(shù)定義,避免箭頭函數(shù)(可讀性更好)
// 推薦 async function loadData() {} // 不推薦 const loadData = async () => {}
- 變量命名:await后的變量名建議體現(xiàn)異步操作含義,如
await fetchResponse
2. 錯誤處理規(guī)范
- 必用try…catch:所有await操作必須包裹在try塊中,catch統(tǒng)一處理錯誤
async function main() { try { const data = await fetchData(); await processData(data); } catch (error) { console.error("全局錯誤捕獲:", error); // 記錄錯誤日志(如上報到監(jiān)控系統(tǒng)) sendErrorToMonitor(error); } }
- 錯誤分類處理:根據(jù)錯誤類型做不同處理(示例)
async function handleError() { try { // ... } catch (error) { if (error instanceof NetworkError) { console.log("網(wǎng)絡錯誤,重試中..."); // 重試邏輯 } else if (error instanceof AuthError) { console.log("認證失敗,跳轉登錄頁"); // 跳轉邏輯 } else { console.error("未知錯誤:", error); } } }
3. 性能優(yōu)化規(guī)范
- 并行任務用Promise.all:避免順序等待浪費時間
// 差:順序執(zhí)行(總耗時=3s+2s=5s) async function bad() { await delay(3000); await delay(2000); } // 好:并行執(zhí)行(總耗時=3s) async function good() { await Promise.all([delay(3000), delay(2000)]); }
- 限制并發(fā)數(shù)量:處理大量并行請求時控制并發(fā)數(shù)(示例)
async function fetchWithConcurrency(urls, maxConcurrent) { const results = []; const fetchQueue = urls.map(url => { return async () => { try { const res = await fetch(url); results.push(await res.json()); } catch (err) { results.push(err); } }; }); // 分批執(zhí)行 for (let i = 0; i < fetchQueue.length; i += maxConcurrent) { const batch = fetchQueue.slice(i, i + maxConcurrent); await Promise.all(batch.map(fetchFn => fetchFn())); } return results; }
4. 代碼風格規(guī)范
- 一行一await:每個await單獨一行,提高可讀性
// 推薦 const response = await fetch(url); const data = await response.json(); // 不推薦 const data = await await fetch(url).then(res => res.json());
- 合理使用return:及時return避免不必要的等待
async function getData() { if (cacheAvailable) { return getFromCache(); // 直接返回緩存數(shù)據(jù),無需等待 } const data = await fetchFromServer(); return data; }
5. 特殊場景處理
- 處理超時:為await操作添加超時控制
// 封裝超時Promise function timeout(ms) { return new Promise((_, reject) => { setTimeout(() => reject(new Error("操作超時")), ms); }); } async function fetchWithTimeout(url, timeoutMs) { try { return await Promise.race([ fetch(url), timeout(timeoutMs) ]); } catch (error) { console.error("請求超時:", error); throw error; } }
- 處理取消操作:使用AbortController取消異步請求
async function fetchWithAbort(url) { const controller = new AbortController(); const signal = controller.signal; // 5秒后取消請求 setTimeout(() => { controller.abort(); console.log("請求已取消"); }, 5000); try { const response = await fetch(url, { signal }); return await response.json(); } catch (error) { if (error.name === "AbortError") { console.log("請求被取消"); } else { console.error("請求失敗:", error); } } }
五、瀏覽器兼容性與解決方案
1. 主流瀏覽器支持情況
瀏覽器 | 最低支持版本 | 支持時間 |
---|---|---|
Chrome | 55+ | 2016年12月 |
Firefox | 52+ | 2017年3月 |
Safari | 11+ | 2017年9月 |
Edge | 15+ | 2017年4月 |
Opera | 42+ | 2016年12月 |
2. 兼容性解決方案
方案一:使用babel轉譯
- 安裝依賴:
npm install @babel/core @babel/preset-env @babel/cli -D
- 創(chuàng)建
.babelrc
配置文件:{ "presets": [ ["@babel/preset-env", { "targets": { "browsers": ["last 2 versions", "ie >= 11"] } }] ] }
- 轉譯命令:
npx babel src -d dist
方案二:引入polyfill
- 安裝
regenerator-runtime
:npm install regenerator-runtime
- 在代碼中引入:
import "regenerator-runtime/runtime";
六、進階技巧與面試高頻問題
1. 如何處理Promise.all中的部分失?。?/h3>
async function fetchWithPartialFail(urls) {
const results = [];
for (const url of urls) {
try {
const res = await fetch(url);
results.push(await res.json());
} catch (error) {
results.push({ error: true, message: error.message });
console.log(`請求${url}失敗`, error);
}
}
return results;
}
async function fetchWithPartialFail(urls) { const results = []; for (const url of urls) { try { const res = await fetch(url); results.push(await res.json()); } catch (error) { results.push({ error: true, message: error.message }); console.log(`請求${url}失敗`, error); } } return results; }
2. await能在普通函數(shù)中使用嗎?為什么?
不能。因為await
必須在async
函數(shù)內使用,本質上async
函數(shù)會被編譯為生成器函數(shù)(Generator),而await
是生成器中yield
的語法糖,普通函數(shù)無法使用該特性。
3. async/await和Promise的本質區(qū)別?
async/await
是語法糖,底層仍基于Promise實現(xiàn)async/await
讓異步代碼擁有同步的寫法,更符合人類思維習慣async/await
通過try...catch
統(tǒng)一處理錯誤,比Promise的.catch()
更直觀
七、總結:async/await使用三原則
- 能用async/await就不用傳統(tǒng)Promise:除非需要處理極其復雜的異步流程控制
- 每個await必配try…catch:避免未捕獲的異步錯誤導致程序崩潰
- 合理選擇并行/順序執(zhí)行:IO密集型任務用Promise.all并行處理,計算密集型任務考慮Web Worker
掌握async/await不僅能寫出更優(yōu)雅的異步代碼,還能提升效率。
到此這篇關于JavaScript async/await的使用場景與規(guī)范的文章就介紹到這了,更多相關JS async/await指南內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JS/HTML5游戲常用算法之路徑搜索算法 A*尋路算法完整實例
這篇文章主要介紹了JS/HTML5游戲常用算法之路徑搜索算法 A*尋路算法,結合完整實例形式分析了A*尋路算法的具體實現(xiàn)技巧,代碼備有詳盡的注釋便于理解,需要的朋友可以參考下2018-12-12Typescript高級類型Record,Partial,Readonly詳解
這篇文章主要介紹了Typescript高級類型Record,Partial,Readonly等介紹,keyof將一個類型的屬性名全部提取出來當做聯(lián)合類型,本文通過實例代碼給大家詳細講解需要的朋友可以參考下2022-11-11javascript實現(xiàn)的LI列表輸出,隔行同色的代碼
javascript實現(xiàn)的LI列表輸出,隔行同色的代碼...2007-10-10