LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

【W(wǎng)eb前端開發(fā)】JavaScript 不要再這樣編寫 async/await

admin
2024年12月30日 12:0 本文熱度 676

最開始接觸 async/await 時(shí),很多人都會(huì)發(fā)出“終于有這個(gè)功能了!”的感嘆。它的語法清晰、可讀性強(qiáng),用起來直觀又順手。

然而,用得越久,就會(huì)發(fā)現(xiàn)一些常見的“坑”時(shí)常在各種項(xiàng)目里出現(xiàn):有些是代碼審查時(shí)發(fā)現(xiàn)的,有些是和同事討論時(shí)暴露的問題。這些都說明異步編程本質(zhì)上并不簡(jiǎn)單。

下文就結(jié)合實(shí)際經(jīng)驗(yàn),列出了一些常見的異步陷阱,以及更高級(jí)的用法與思考方式,讓代碼更健壯,也更易維護(hù)。


從回調(diào)地獄到 async/await

還記得當(dāng)初的回調(diào)地獄嗎?JavaScript 進(jìn)化到現(xiàn)在,已經(jīng)讓我們避免了深層嵌套的回調(diào)結(jié)構(gòu)。

但功能變強(qiáng)大了,責(zé)任也跟著變大。下面是 async/await 中常見的三大“致命罪狀”。


1. 同步式的瀑布請(qǐng)求

糟糕示例:順序等待

在代碼審查里經(jīng)常看到這樣的場(chǎng)景:本來可以并發(fā)執(zhí)行的請(qǐng)求,卻被一個(gè)接一個(gè)地串行處理。

// ?? 不推薦
async function loadDashboard({
  const user = await fetchUser();
  const posts = await fetchPosts();
  const notifications = await fetchNotifications();
}

如果每個(gè)請(qǐng)求都要 200ms,這里就總共要 600ms,給用戶的體驗(yàn)自然不佳。

改進(jìn):并發(fā)執(zhí)行

對(duì)于相互獨(dú)立的操作,應(yīng)該使用并發(fā)來節(jié)省時(shí)間:

// ? 建議用 Promise.all
async function loadDashboard({
  const [user, posts, notifications] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchNotifications()
  ]);
}

同樣是獲取用戶、帖子和通知,響應(yīng)速度立刻加快三倍左右。不過,并發(fā)并非萬能。

  • 依賴關(guān)系:如果一個(gè)請(qǐng)求需要另一個(gè)請(qǐng)求返回的數(shù)據(jù),就必須順序執(zhí)行。
  • 競(jìng)態(tài)條件:如果多個(gè)請(qǐng)求會(huì)同時(shí)修改某個(gè)共享資源,可能導(dǎo)致數(shù)據(jù)不一致。
  • 資源限制:并發(fā)過多會(huì)給服務(wù)器或?yàn)g覽器帶來壓力。
舉例:危險(xiǎn)的并發(fā)
// ?? 并行可能導(dǎo)致競(jìng)態(tài)問題
await Promise.all([
  updateUserProfile(userId, { name'New Name' }),
  updateUserProfile(userId, { email'new@email.com' })
]);

// ? 先后執(zhí)行以防數(shù)據(jù)沖突
const user = await updateUserProfile(userId, { name'New Name' });
await updateUserProfile(userId, { email'new@email.com' });

如果并行更新同一個(gè)用戶資料,服務(wù)器可能會(huì)出現(xiàn)覆蓋數(shù)據(jù)的情況。必須根據(jù)業(yè)務(wù)邏輯判斷能否并發(fā)執(zhí)行。


2. 隱形錯(cuò)誤:如何平衡異常處理

常見誤區(qū):把錯(cuò)誤直接“吞掉”

不少人喜歡在 catch 塊里寫個(gè)簡(jiǎn)單的 console.error(error),然后返回 null,讓外層調(diào)用時(shí)貌似一切正常。

// ?? 隱形錯(cuò)誤
async function fetchData({
  try {
    const response = await fetch('/api/data');
    return await response.json();
  } catch (error) {
    console.error(error);
    return null;  // ?? 難以排查的隱患
  }
}

看似“處理”了錯(cuò)誤,但實(shí)際上把錯(cuò)誤原因都藏起來了。網(wǎng)絡(luò)斷了?JSON 解析失敗?服務(wù)器返回 500?外部代碼只能拿到 null,毫無頭緒。

更好的做法:區(qū)分場(chǎng)景處理

返回空值并非一直不對(duì)。如果它只是一個(gè)不關(guān)鍵的功能,比如推薦列表或活動(dòng)通知,給用戶一個(gè)空狀態(tài)也許是更友好的方式。但如果是核心數(shù)據(jù),就應(yīng)該拋出異常或者做更明確的錯(cuò)誤處理,讓上層邏輯感知到問題。

高級(jí)開發(fā)者通常會(huì)這樣寫:

// ? 有針對(duì)性的錯(cuò)誤處理
async function fetchData(options = {}{
  const { isCritical = true } = options;
  
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('Data fetch failed:', error);
    
    if (isCritical) {
      // 對(duì)關(guān)鍵數(shù)據(jù)拋出自定義錯(cuò)誤,交由更高層級(jí)處理
      throw new ApiError('Failed to fetch critical data', { cause: error });
    } else {
      // 非關(guān)鍵數(shù)據(jù),可返回一個(gè)降級(jí)的默認(rèn)值
      return { type'fallback'data: [] };
    }
  }
}

// 用法示例:
// 關(guān)鍵數(shù)據(jù)(出錯(cuò)時(shí)會(huì)拋異常)
const userData = await fetchData({ isCriticaltrue });

// 非關(guān)鍵通知數(shù)據(jù)(出錯(cuò)時(shí)返回空列表)
const notifications = await fetchData({ isCriticalfalse });

這樣就能同時(shí)兼顧穩(wěn)定性和可維護(hù)性。關(guān)鍵數(shù)據(jù)絕不能“悄悄失敗”,而次要功能可以“優(yōu)雅退化”。


3. 內(nèi)存泄漏的陷阱和現(xiàn)代化的清理方式

典型誤區(qū):無休止的輪詢

假設(shè)寫了一個(gè)定時(shí)輪詢,幾秒鐘拉取一次數(shù)據(jù):

// ?? 隱形的內(nèi)存殺手
async function startPolling({
  setInterval(async () => {
    const data = await fetchData();
    updateUI(data);
  }, 5000);
}

表面看上去沒什么問題,但這樣會(huì)導(dǎo)致:

  • 組件或頁面卸載后依然在輪詢
  • 如果 fetchData() 執(zhí)行得很慢,可能會(huì)同時(shí)發(fā)起多次請(qǐng)求
  • 更新 UI 時(shí),目標(biāo) DOM 甚至可能已經(jīng)被移除
改進(jìn):AbortController + 輪詢管理

下面這個(gè)示例借助 AbortController 實(shí)現(xiàn)了更安全的輪詢:

class PollingManager {
  constructor(options = {}) {
    this.controller = new AbortController();
    this.interval = options.interval || 5000;
  }

  async start() {
    while (!this.controller.signal.aborted) {
      try {
        const response = await fetch('/api/data', {
          signalthis.controller.signal
        });
        const data = await response.json();
        updateUI(data);
        
        // 等待下一次輪詢
        await new Promise(resolve => setTimeout(resolve, this.interval));
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Polling stopped');
          return;
        }
        console.error('Polling error:', error);
      }
    }
  }

  stop() {
    this.controller.abort();
  }
}

// 在 React 組件中使用
function DataComponent({
  useEffect(() => {
    const poller = new PollingManager({ interval5000 });
    poller.start();
    
    // 組件卸載時(shí)停止輪詢
    return () => poller.stop();
  }, []);
  
  return <div>Data: {/* ... */}</div>;
}

通過使用 AbortController,可以在需要時(shí)終止請(qǐng)求并及時(shí)釋放資源,更好地控制組件的生命周期和內(nèi)存占用。


高級(jí)開發(fā)者的工具箱

1. 重試(Retry)模式

網(wǎng)絡(luò)環(huán)境不穩(wěn)定或第三方服務(wù)時(shí)好時(shí)壞的情況下,只嘗試一次就放棄不是好辦法。可以加上重試和退避策略:

async function fetchWithRetry(url, options = {}{
  const { maxRetries = 3, backoff = 1000 } = options;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fetch(url);
    } catch (error) {
      if (attempt === maxRetries - 1throw error;
      // 退避延遲,指數(shù)增長(zhǎng)
      await new Promise(r => setTimeout(r, backoff * Math.pow(2, attempt)));
    }
  }
}

除了基本的指數(shù)退避,還可以考慮:

  • 避免過度重試導(dǎo)致資源浪費(fèi)
  • 區(qū)分哪些錯(cuò)誤類型才需要重試
  • 使用斷路器(Circuit Breaker)模式保護(hù)系統(tǒng)
  • 加入隨機(jī)抖動(dòng)(Jitter)防止大量請(qǐng)求同時(shí)重試

2. 資源管理

啟動(dòng)異步操作簡(jiǎn)單,關(guān)鍵是如何“優(yōu)雅地”停止它們。通過統(tǒng)一的 ResourceManager 或類似模式,可以集中處理一些關(guān)閉連接、清理定時(shí)器、取消任務(wù)等邏輯:

class ResourceManager {
  cleanup() {
    // 在這里集中處理各種操作的終止和釋放
  }
}

真實(shí)場(chǎng)景中的模式

1. 統(tǒng)一的加載狀態(tài)管理

不要在每個(gè)組件都寫一堆 “正在加載”、“錯(cuò)誤” 判斷。可以抽象出一個(gè)自定義 Hook 或者統(tǒng)一的加載管理邏輯:

const { data, loading, error } = useAsyncData(() => fetchUserData());

這樣可以:

  • 保持加載和錯(cuò)誤狀態(tài)處理的一致性
  • 便于集中管理和優(yōu)化
  • 在組件卸載時(shí)自動(dòng)清理

2. 數(shù)據(jù)同步器(Data Synchronizer)

對(duì)于實(shí)時(shí)性要求高的應(yīng)用,與其一個(gè)個(gè)寫請(qǐng)求,不如建立一個(gè)數(shù)據(jù)同步管理器,統(tǒng)一處理輪詢/訂閱/數(shù)據(jù)合并等邏輯:

const syncManager = new DataSyncManager({
  onSync(data) => updateUI(data),
  onError(error) => showError(error),
  syncInterval5000
});

幾條核心原則

  1. 并發(fā)原則

    • 讓互不依賴的操作同時(shí)執(zhí)行
    • 小心競(jìng)態(tài)和依賴順序
    • 不要為了并發(fā)而并發(fā),要考慮業(yè)務(wù)邏輯
  2. 彈性原則(Resilience)

    • 及時(shí)重試和錯(cuò)誤處理
    • 做好網(wǎng)絡(luò)或服務(wù)不可靠的準(zhǔn)備
    • 避免一個(gè)錯(cuò)誤拖垮整個(gè)系統(tǒng)
  3. 資源管理原則

    • 主動(dòng)清理異步操作
    • 終止不再需要的請(qǐng)求
    • 防止內(nèi)存泄漏
  4. 用戶體驗(yàn)原則

    • 有意義的加載提示和錯(cuò)誤信息
    • 保證核心功能的可用性
    • 在可能的情況下提供取消或中斷操作

常見問題

1. 什么時(shí)候用 Promise.all,什么時(shí)候用 Promise.allSettled

  • Promise.all 適合所有請(qǐng)求都必須成功的場(chǎng)景
  • Promise.allSettled 允許部分失敗,適合容忍部分請(qǐng)求出錯(cuò)的需求
  • Promise.race 則常用于超時(shí)等情況

2. 在 React 中如何優(yōu)雅地清理?

  • 善用 useEffect 的返回值進(jìn)行清理
  • 使用 AbortController 終止 HTTP 請(qǐng)求
  • 組件卸載時(shí),保證所有定時(shí)器、訂閱或輪詢都能停止

展望

  • 檢查代碼:查找本可并發(fā)卻寫成串行的請(qǐng)求;檢查錯(cuò)誤處理是否含糊不清;關(guān)注異步操作的清理是否充分。
  • 改進(jìn)模式:引入更健壯的錯(cuò)誤處理,增加重試邏輯,優(yōu)化資源釋放。
  • 考慮擴(kuò)展性:當(dāng)用戶量或請(qǐng)求量激增時(shí),如何保證依舊能流暢運(yùn)行?如果某些服務(wù)變慢甚至掛掉,該如何部分降級(jí)?

為什么要在意這些細(xì)節(jié)

或許有人會(huì)說,“我的小項(xiàng)目沒這么復(fù)雜,用不著搞這些”。但真正的好代碼是能經(jīng)得住放大和演進(jìn)的。

  • 可擴(kuò)展性:面對(duì)更多用戶和請(qǐng)求,系統(tǒng)能否穩(wěn)健地運(yùn)行
  • 用戶體驗(yàn):高并發(fā)、良好錯(cuò)誤處理能讓應(yīng)用體驗(yàn)更流暢
  • 開發(fā)者體驗(yàn):清晰的異步邏輯有助于日后維護(hù)和團(tuán)隊(duì)協(xié)作
  • 資源利用:合理的并發(fā)和清理機(jī)制能節(jié)約服務(wù)器和客戶端資源

這些并不是紙上談兵,而是大量實(shí)戰(zhàn)總結(jié)出來的硬道理。隨著項(xiàng)目的規(guī)模和復(fù)雜度不斷提升,這些異步編程模式會(huì)是你寫出高質(zhì)量前端代碼的核心基石。


該文章在 2024/12/30 12:24:25 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
日本少妇一区二区三区 | 久久se精品一区二区三区 | 日本一区二区久久 | 日韩精品一区二区三区视频免费看 | 亚洲国产在一区二区三区 | 亚洲欧美丝袜精品久久中文字幕 |