Langkau ke kandungan
WANCODE
EN

Fetch dengan Had Masa (AbortController)

Berhenti menunggu request yang lambat: batalkan fetch() selepas had masa, sokong butang batal, dan bezakan antara keduanya.

fetch-with-timeout.js
/**
 * 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 kepada fetch.
  • setTimeout membatalkan dengan DOMException bernama TimeoutError, jadi pemanggil boleh bezakan "terlalu lambat" dengan "pengguna batalkan" (AbortError).
  • signal dari luar disalurkan ke dalam controller kita, jadi kedua-dua pemasa dan butang batal anda boleh hentikan request yang sama.
  • finally sentiasa 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

JavaScript
const res = await fetchWithTimeout("/api/items", {
  timeout: 5000,
  signal: cancelController.signal,
});

Pintasan terbina (pelayar baharu)

JavaScript
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

JavaScript
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 [];
  }
}