你真的該在 JavaScript 里“多取消”
某天我在做一個 Web 應(yīng)用:三個 fetch 并發(fā),用戶還沒等結(jié)果出來就切走了頁面。然后呢?這三條請求仍舊跑到完結(jié)——帶寬被浪費(fèi)、CPU 被占用、用戶也得不到任何好處。
那一刻我意識到:異步任務(wù)的“取消”,不是可選項(xiàng),而是必選項(xiàng)。 你是否也經(jīng)歷過這種“用戶一走了之、網(wǎng)絡(luò)卻在后臺堵車”的場景?
我需要一個能及時叫停異步操作的機(jī)制。
接下來看看這個很酷的 JavaScript 能力,如何把異步的生殺大權(quán)交回到你手里——因此應(yīng)用更快、同時用戶更開心、而且服務(wù)器壓力更小。
機(jī)制揭秘:它是如何工作的
主角叫 AbortController。這是控制異步取消的原生 API。
它的思路非常樸素:創(chuàng)建一個 controller,拿到其中的 signal,把這個信號傳給需要“可取消”的異步操作(例如 fetch,也可以是你封裝的自定義異步函數(shù),甚至是計時器)。當(dāng)你在合適的時機(jī)調(diào)用 abort(),所有與該 signal 關(guān)聯(lián)的任務(wù)都會被終止。
一個極簡示例:
const controller = new AbortController();
const { signal } = controller;
fetch('/api/data', { signal })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Fetch canceled');
    }
  });
// 稍后……
controller.abort(); // 取消該次請求signal 把 fetch 和控制器綁在一起;abort() 一下,立刻“掐斷”。因此,當(dāng)用戶已離場,我們就不必讓請求在后臺無意義地跑完。
真實(shí)場景:搜索請求的“競態(tài)清除”
你可能寫過即時搜索:用戶每敲一個字符就發(fā)一次請求。如果不取消舊請求,競態(tài)隨時發(fā)生——“a”的響應(yīng)可能晚于“abc”,最后把正確結(jié)果又覆蓋掉了。
用 AbortController,每次新請求發(fā)出時先取消舊的:
let controller = null;
function search(query) {
  // 1) 取消上一次未完成的請求
  if (controller) controller.abort();
  // 2) 為本次查詢創(chuàng)建新控制器
  controller = new AbortController();
  fetch(`https://api.example.com/search?q=${query}`, { signal: controller.signal })
    .then(response => response.json())
    .then(data => console.log('Results:', data))
    .catch(err => {
      if (err.name === 'AbortError') return; // 預(yù)期中的取消,不算錯誤
      console.error('Error:', err);
    });
}
// 連續(xù)輸入的模擬
search('cat');
search('cats'); // 'cat' 的請求被取消,只保留最新的結(jié)果呢?只有最新那次查詢會落地,因此后端不再被“鍵盤風(fēng)暴”轟炸,同時前端也能避免錯亂刷新,從而讓列表穩(wěn)定且準(zhǔn)確。
不止網(wǎng)絡(luò):計時器同樣可控
AbortController絕不是fetch 的專屬。事實(shí)上,你可以把它接到幾乎任何異步流程上。下面是一個“用戶離開就掐掉超時器”的小例子:
const controller = new AbortController();
const { signal } = controller;
function delay(ms, signal) {
  return new Promise((resolve, reject) => {
    const id = setTimeout(resolve, ms);
    signal.addEventListener("abort", () => {
      clearTimeout(id);
      reject(new Error("Timeout canceled"));
    });
  });
}
delay(5000, signal)
  .then(() => console.log("Done"))
  .catch(console.error);
// 在 5 秒之前取消
controller.abort();這樣一來,不再需要那些“沒人等、卻一直掛著”的計時器;因此副作用更少,而且可回收性更好。
為什么這件事如此重要?
使用 AbortController 會讓你的前端更“干凈”:
- 性能:殺掉沒人需要的請求;因此節(jié)省帶寬與 CPU。
 - 體驗(yàn):不再閃爍、不再“過期數(shù)據(jù)”覆蓋新結(jié)果;同時加載狀態(tài)可控。
 - 可控性:由你決定哪些異步繼續(xù)、哪些該終止——從而避免連鎖反應(yīng)與資源泄漏。
 
但也別忽略它的邊界
- 舊瀏覽器兼容性:在 2018 年之前的環(huán)境里,
AbortController支持并不理想;因此需要 polyfill 或降級策略。 - 部分 API 不支持 
signal:例如某些老的 WebSocket 實(shí)現(xiàn)與第三方 SDK;不過你仍可以在外層封裝一個“軟取消”(即忽略結(jié)果)來達(dá)成類似效果。 - 團(tuán)隊規(guī)范:取消語義需要貫穿調(diào)用棧;否則容易出現(xiàn)“上層以為取消了、下層仍在跑”的錯位——因此請為自定義異步函數(shù)設(shè)計 
signal參數(shù)并妥善傳播。 
最后的結(jié)論
我曾經(jīng)把無用請求當(dāng)成 Web 的“自然損耗”。而現(xiàn)在,只要是交互密集的頁面,我幾乎不會再寫沒有“取消通道”的異步。
試著在下一個項(xiàng)目里用用 AbortController:因此你的網(wǎng)絡(luò)更清爽,同時用戶看到的內(nèi)容更穩(wěn)定,最終你的應(yīng)用會顯得更專業(yè)。















 
 
 









 
 
 
 