2022 年 4 月 13 日

取得:中止

正如我們所知,fetch 會傳回一個承諾。而 JavaScript 通常沒有「中止」承諾的概念。那麼,我們要如何取消正在進行的 fetch?例如,如果使用者在我們網站上的動作表示不再需要 fetch

有特別內建的物件可供此用途:AbortController。它不僅可用來中止 fetch,還能中止其他非同步任務。

用法非常簡單

AbortController 物件

建立控制器

let controller = new AbortController();

控制器是一個非常簡單的物件。

  • 它有一個單一方法 abort()
  • 和一個單一屬性 signal,允許在上面設定事件聆聽器。

當呼叫 abort() 時,

  • controller.signal 發出 "abort" 事件。
  • controller.signal.aborted 屬性變為 true

通常,此程序中有兩方

  1. 執行可取消操作的一方會在 controller.signal 上設定監聽器。
  2. 取消的一方:在需要時呼叫 controller.abort()

以下是完整範例(尚未包含 fetch

let controller = new AbortController();
let signal = controller.signal;

// The party that performs a cancelable operation
// gets the "signal" object
// and sets the listener to trigger when controller.abort() is called
signal.addEventListener('abort', () => alert("abort!"));

// The other party, that cancels (at any point later):
controller.abort(); // abort!

// The event triggers and signal.aborted becomes true
alert(signal.aborted); // true

正如我們所見,AbortController 僅是呼叫其上的 abort() 時傳遞 abort 事件的方法。

我們可以在自己的程式碼中實作相同類型的事件監聽,而不需要 AbortController 物件。

但有價值的是,fetch 知道如何使用 AbortController 物件。它已整合在其中。

與 fetch 搭配使用

若要取消 fetch,請將 AbortControllersignal 屬性傳遞為 fetch 選項

let controller = new AbortController();
fetch(url, {
  signal: controller.signal
});

fetch 方法知道如何使用 AbortController。它會監聽 signal 上的 abort 事件。

現在,若要中止,請呼叫 controller.abort()

controller.abort();

我們完成了:fetchsignal 取得事件,並中止要求。

中止 fetch 時,其承諾會以 AbortError 錯誤拒絕,因此我們應該處理它,例如在 try..catch 中。

以下是 1 秒後中止 fetch 的完整範例

// abort in 1 second
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);

try {
  let response = await fetch('/article/fetch-abort/demo/hang', {
    signal: controller.signal
  });
} catch(err) {
  if (err.name == 'AbortError') { // handle abort()
    alert("Aborted!");
  } else {
    throw err;
  }
}

AbortController 可擴充

AbortController 可擴充。它允許一次取消多個 fetch

以下是同時擷取多個 url 並使用單一控制器中止所有 url 的程式碼草稿

let urls = [...]; // a list of urls to fetch in parallel

let controller = new AbortController();

// an array of fetch promises
let fetchJobs = urls.map(url => fetch(url, {
  signal: controller.signal
}));

let results = await Promise.all(fetchJobs);

// if controller.abort() is called from anywhere,
// it aborts all fetches

如果我們有自己的非同步工作,不同於 fetch,我們可以使用單一 AbortController 來停止這些工作,以及 fetch

我們只需在工作中監聽其 abort 事件

let urls = [...];
let controller = new AbortController();

let ourJob = new Promise((resolve, reject) => { // our task
  ...
  controller.signal.addEventListener('abort', reject);
});

let fetchJobs = urls.map(url => fetch(url, { // fetches
  signal: controller.signal
}));

// Wait for fetches and our task in parallel
let results = await Promise.all([...fetchJobs, ourJob]);

// if controller.abort() is called from anywhere,
// it aborts all fetches and ourJob

摘要

  • AbortController 是個簡單的物件,當呼叫 abort() 方法時,會在其 signal 屬性上產生 abort 事件(也會將 signal.aborted 設定為 true)。
  • fetch 與其整合:我們將 signal 屬性傳遞為選項,然後 fetch 會監聽它,因此可以中止 fetch
  • 我們可以在程式碼中使用 AbortController。呼叫 abort() → 監聽 abort 事件的互動很簡單且通用。我們甚至可以在沒有 fetch 的情況下使用它。
教學課程地圖

留言

在留言前請先閱讀…
  • 如果您有改進建議,請 提交 GitHub 問題 或發起拉取請求,而不是留言。
  • 如果您無法理解文章中的內容,請詳細說明。
  • 若要插入一些程式碼,請使用 <code> 標籤,若要插入多行程式碼,請將其包覆在 <pre> 標籤中,若要插入超過 10 行的程式碼,請使用沙盒 (plnkrjsbincodepen…)