AbortController介紹
一、什麼是 AbortController?
AbortController
是瀏覽器內建的一個 API,用來 控制取消操作。
最常見的用途就是搭配 fetch
,當你發送網路請求後,如果在某些情況下不需要回應(例如:使用者跳頁、搜尋框輸入新關鍵字等),就可以用它來「中止」請求,避免浪費網路資源或產生不必要的結果。
二、AbortController 的組成
-
AbortController
這是一個「控制器」,可以建立「信號(signal)」並負責觸發取消事件。 -
AbortSignal
這是「信號物件」,代表某個操作是否被中止。當你呼叫controller.abort()
,這個 signal 就會通知對應的操作要結束。
const controller = new AbortController();
const signal = controller.signal;
三、基本使用範例
1. 搭配 fetch
const controller = new AbortController();
const signal = controller.signal;
fetch("https://jsonplaceholder.typicode.com/posts", { signal })
.then(response => response.json())
.then(data => console.log("成功回應:", data))
.catch(err => {
if (err.name === "AbortError") {
console.log("請求已被中止");
} else {
console.error("其他錯誤:", err);
}
});
// 在 1 秒後中止請求
setTimeout(() => {
controller.abort();
}, 1000);
👉 說明:
-
建立
AbortController
,並把signal
傳給fetch
。 -
如果
controller.abort()
被呼叫,fetch
就會丟出一個AbortError
。 -
這樣就可以在特定情況下結束請求,避免繼續消耗資源。
2. 搭配事件監聽器
AbortController
不只可以用在 fetch
,也能用在支援 AbortSignal 的 API,例如事件監聽器。
const controller = new AbortController();
const signal = controller.signal;
document.addEventListener("click", () => {
console.log("文件被點擊!");
}, { signal });
// 中止後事件監聽器會自動移除
setTimeout(() => {
controller.abort();
console.log("點擊事件監聽器已被移除");
}, 3000);
四、常見使用情境
-
搜尋框即時查詢
使用者輸入新關鍵字時,取消舊的 API 請求,避免多餘的結果回來。 -
頁面跳轉
如果使用者離開當前頁面,不需要再等待 API 回應,直接中止請求。 -
多重事件監聽管理
利用AbortSignal
可以更乾淨地管理監聽器,而不用自己手動呼叫removeEventListener
。
五、總結
-
AbortController
提供 中止操作 的能力。 -
controller.signal
要傳給支援的 API(例如fetch
或事件監聽器)。 -
呼叫
controller.abort()
就會通知相關操作中止。 -
常見於 取消請求、清除事件監聽器,讓程式更有效率、更乾淨。
React Hooks 範例:搜尋框自動取消前一次的 fetch
import React, { useState, useEffect } from "react";
export default function SearchBox() {
const [query, setQuery] = useState(""); // 使用者輸入的關鍵字
const [results, setResults] = useState([]); // API 回傳的結果
const [loading, setLoading] = useState(false); // 是否正在載入中
const [error, setError] = useState(null); // 錯誤訊息
useEffect(() => {
if (!query) {
setResults([]);
return;
}
const controller = new AbortController(); // 建立控制器
const signal = controller.signal;
const fetchData = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts?q=${query}`,
{ signal }
);
if (!response.ok) {
throw new Error("網路回應失敗");
}
const data = await response.json();
setResults(data); // 更新結果(immutable 更新)
} catch (err) {
if (err.name === "AbortError") {
console.log("前一次的請求已被中止");
} else {
setError(err.message);
}
} finally {
setLoading(false);
}
};
fetchData();
// cleanup:在 effect 被下一次觸發時,中止前一次請求
return () => {
controller.abort();
};
}, [query]); // query 改變時才會觸發新的 fetch
return (
<div style={{ padding: "20px" }}>
<h2>搜尋文章</h2>
<input
type="text"
placeholder="輸入關鍵字..."
value={query}
onChange={(e) => setQuery(e.target.value)}
style={{ width: "300px", padding: "8px", fontSize: "16px" }}
/>
{loading && <p>載入中...</p>}
{error && <p style={{ color: "red" }}>錯誤:{error}</p>}
<ul>
{results.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
程式重點解析
-
每次輸入
query
改變時,useEffect 都會重新觸發 → 開始一個新的fetch
。 -
cleanup 機制 (
return () => controller.abort()
) → 保證舊的請求會被取消。 -
錯誤判斷 → 只有
AbortError
才忽略,其他錯誤要顯示給使用者。 -
immutable 更新 →
setResults(data)
直接建立新的陣列,避免修改舊的 state。 -
狀態切分 →
loading
、error
、results
都分開 state,讓 UI 乾淨。