Fetch dengan Had Masa (AbortController)
Berhenti menunggu request yang lambat: batalkan fetch() selepas had masa, sokong butang batal, dan bezakan antara keduanya.
/**
* fetch() that gives up after `timeout` ms.
* Also accepts your own AbortSignal (e.g. a "Cancel" button).
*/
export async function fetchWithTimeout(resource, { timeout = 8000, signal, ...options } = {}) {
const controller = new AbortController();
const timer = setTimeout(() => {
controller.abort(new DOMException("Request timed out after " + timeout + " ms", "TimeoutError"));
}, timeout);
// Forward an outside signal into our controller
const forwardAbort = () => controller.abort(signal.reason);
if (signal) {
if (signal.aborted) forwardAbort();
else signal.addEventListener("abort", forwardAbort, { once: true });
}
try {
return await fetch(resource, { ...options, signal: controller.signal });
} finally {
clearTimeout(timer);
signal?.removeEventListener("abort", forwardAbort);
}
}fetch() tiada pilihan had masa (timeout). Pada sambungan yang lemah ia boleh menunggu sangat lama sebelum gagal. AbortController beri anda satu signal untuk dihantar kepada fetch; memanggil controller.abort() menyebabkan fetch itu terus ditolak (reject).
Baris demi baris
{ timeout = 8000, signal, ...options }asingkan pilihan kita sendiri dan simpan selebihnya (method, headers, body) untuk terus dihantar kepadafetch.setTimeoutmembatalkan denganDOMExceptionbernamaTimeoutError, jadi pemanggil boleh bezakan "terlalu lambat" dengan "pengguna batalkan" (AbortError).signaldari luar disalurkan ke dalam controller kita, jadi kedua-dua pemasa dan butang batal anda boleh hentikan request yang sama.finallysentiasa kosongkan pemasa dan buang listener — sama ada request berjaya, gagal atau dibatalkan — jadi tiada yang tertinggal (leak).
Dua perkara yang fetch tak buat untuk anda
1) 404 atau 500 bukan ralat bagi fetch — semak response.ok. 2) Had masa di sini meliputi tempoh menunggu header respons; membaca body yang besar selepas itu (response.json()) tidak diliputi selepas pemasa dikosongkan.
Fungsi bantu ini
const res = await fetchWithTimeout("/api/items", {
timeout: 5000,
signal: cancelController.signal,
});Pintasan terbina (pelayar baharu)
const res = await fetch("/api/items", {
signal: AbortSignal.any([
AbortSignal.timeout(5000),
cancelController.signal,
]),
});AbortSignal.timeout() dan AbortSignal.any() buat kerja yang sama secara asli. AbortSignal.any() lebih baharu (2023–2024), jadi fungsi bantu ini pilihan lebih selamat jika anda menyokong pelayar lama.
Tiada preview langsung untuk yang ini: request rangkaian sebenar dimatikan dalam preview WANCode (lihat Cara kod anda dijalankan), jadi had masa tak boleh ditunjukkan dengan jujur di situ. Cuba dalam projek tempatan.
Cara guna
import { fetchWithTimeout } from "./fetch-with-timeout.js";
async function loadItems() {
try {
const response = await fetchWithTimeout("/api/items", { timeout: 5000 });
if (!response.ok) throw new Error("Server replied " + response.status);
return await response.json();
} catch (error) {
if (error.name === "TimeoutError") {
console.warn("The server is taking too long. Please try again.");
} else if (error.name === "AbortError") {
console.info("Request cancelled.");
} else {
console.error("Couldn't load items:", error.message);
}
return [];
}
}