Javascript Fetch:中止(Abort)

2023-02-17 10:57 更新

正如我們所知道的,?fetch? 返回一個 promise。JavaScript 通常并沒有“中止” promise 的概念。那么我們怎樣才能取消一個正在執(zhí)行的 ?fetch? 呢?例如,如果用戶在我們網(wǎng)站上的操作表明不再需要某個執(zhí)行中的 ?fetch?。

為此有一個特殊的內(nèi)建對象:AbortController。它不僅可以中止 fetch,還可以中止其他異步任務(wù)。

用法非常簡單。

AbortController 對象

創(chuàng)建一個控制器(controller):

let controller = new AbortController();

控制器是一個極其簡單的對象。

  • 它具有單個方法 ?abort()?,
  • 和單個屬性 ?signal?,我們可以在這個屬性上設(shè)置事件監(jiān)聽器。

當(dāng) abort() 被調(diào)用時:

  • ?controller.signal? 就會觸發(fā) ?abort? 事件。
  • ?controller.signal.aborted? 屬性變?yōu)?nbsp;?true?。

通常,我們需要處理兩部分:

  1. 一部分是通過在 ?controller.signal? 上添加一個監(jiān)聽器,來執(zhí)行可取消操作。
  2. 另一部分是觸發(fā)取消:在需要的時候調(diào)用 ?controller.abort()?。

這是完整的示例(目前還沒有 fetch):

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

// 執(zhí)行可取消操作部分
// 獲取 "signal" 對象,
// 并將監(jiān)聽器設(shè)置為在 controller.abort() 被調(diào)用時觸發(fā)
signal.addEventListener('abort', () => alert("abort!"));

// 另一部分,取消(在之后的任何時候):
controller.abort(); // 中止!

// 事件觸發(fā),signal.aborted 變?yōu)?true
alert(signal.aborted); // true

正如我們所看到的,AbortController 只是在 abort() 被調(diào)用時傳遞 abort 事件的一種方式。

我們可以自己在代碼中實(shí)現(xiàn)相同類型的事件監(jiān)聽,而不需要 AbortController 對象。

但是有價(jià)值的是,fetch 知道如何與 AbortController 對象一起工作。它們是集成在一起的。

與 fetch 一起使用

為了能夠取消 fetch,請將 AbortController 的 signal 屬性作為 fetch 的一個可選參數(shù)(option)進(jìn)行傳遞:

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

fetch 方法知道如何與 AbortController 一起工作。它會監(jiān)聽 signal 上的 abort 事件。

現(xiàn)在,想要中止 fetch,調(diào)用 controller.abort() 即可:

controller.abort();

我們完成啦:fetch 從 signal 獲取了事件并中止了請求。

當(dāng)一個 fetch 被中止,它的 promise 就會以一個 error AbortError reject,因此我們應(yīng)該對其進(jìn)行處理,例如在 try..catch 中。

這是完整的示例,其中 fetch 在 1 秒后中止:

// 1 秒后中止
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。

這是一個代碼草稿,該代碼并行 fetch 很多 urls,并使用單個控制器將其全部中止:

let urls = [...]; // 要并行 fetch 的 url 列表

let controller = new AbortController();

// 一個 fetch promise 的數(shù)組
let fetchJobs = urls.map(url => fetch(url, {
  signal: controller.signal
}));

let results = await Promise.all(fetchJobs);

// controller.abort() 被從任何地方調(diào)用,
// 它都將中止所有 fetch

如果我們有自己的與 fetch 不同的異步任務(wù),我們可以使用單個 AbortController 中止這些任務(wù)以及 fetch。

在我們的任務(wù)中,我們只需要監(jiān)聽其 abort 事件:

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

let ourJob = new Promise((resolve, reject) => { // 我們的任務(wù)
  ...
  controller.signal.addEventListener('abort', reject);
});

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

// 等待完成我們的任務(wù)和所有 fetch
let results = await Promise.all([...fetchJobs, ourJob]);

// controller.abort() 被從任何地方調(diào)用,
// 它都將中止所有 fetch 和 ourJob

總結(jié)

  • ?AbortController? 是一個簡單的對象,當(dāng) ?abort()? 方法被調(diào)用時,會在自身的 ?signal? 屬性上生成 ?abort? 事件(并將 ?signal.aborted? 設(shè)置為 ?true?)。
  • ?fetch? 與之集成:我們將 ?signal? 屬性作為可選參數(shù)(option)進(jìn)行傳遞,之后 ?fetch? 會監(jiān)聽它,因此它能夠中止 ?fetch?。
  • 我們可以在我們的代碼中使用 ?AbortController??!罢{(diào)用 ?abort()?” → “監(jiān)聽 ?abort? 事件”交互簡單且通用。即使沒有 ?fetch?,我們也可以使用它。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號