Javascript Fetch:中止(Abort)

2023-02-17 10:57 更新

正如我們所知道的,?fetch? 返回一個(gè) promise。JavaScript 通常并沒(méi)有“中止” promise 的概念。那么我們?cè)鯓硬拍苋∠粋€(gè)正在執(zhí)行的 ?fetch? 呢?例如,如果用戶在我們網(wǎng)站上的操作表明不再需要某個(gè)執(zhí)行中的 ?fetch?。

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

用法非常簡(jiǎn)單。

AbortController 對(duì)象

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

let controller = new AbortController();

控制器是一個(gè)極其簡(jiǎn)單的對(duì)象。

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

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

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

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

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

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

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

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

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

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

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

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

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

與 fetch 一起使用

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

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

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

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

controller.abort();

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

當(dāng)一個(gè) fetch 被中止,它的 promise 就會(huì)以一個(gè) error AbortError reject,因此我們應(yīng)該對(duì)其進(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 是可伸縮的。它允許一次取消多個(gè) fetch。

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

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

let controller = new AbortController();

// 一個(gè) 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ù),我們可以使用單個(gè) AbortController 中止這些任務(wù)以及 fetch。

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


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)