当前位置:网站首页>AbortController的使用

AbortController的使用

2022-06-26 15:26:00 flytam

今天介紹一個有用的 JavaScript api AbortController

AbortController是什麼

AbortController 接口錶示一個控制器對象,允許你根據需要中止一個或多個 Web 請求。你可以使用 AbortController.AbortController() 構造函數創建一個新的 AbortController。使用 AbortSignal 對象可以完成與 DOM 請求的通信

這個 api 簡單來說就是可以提供一個能力給我們去提前終止一個 fetch 請求

一個終止 fetch 請求的 demo 如下:

fetchButton.onclick = async () => {
    
  const controller = new AbortController();

  // 點擊abort button實現終止fetch請求
  abortButton.onclick = () => controller.abort();

  try {
    
    const r = await fetch('/json', {
     signal: controller.signal });
    const json = await r.json();
  } catch (e) {
    
  // 如果fetch請求被終止會拋出一個AbortError的錯誤
    const isUserAbort = (e.name === 'AbortError');
  }
};

提前終止後這個請求在 network 面板中的 status 顯示為 canceled

在沒有AbortController這個 api 之前,我們是沒法去讓瀏覽器提前去終止一個請求的。而有了這個 api 之後,瀏覽器就能提前終止請求進而節約一些用戶帶寬。除此之外,這個 api 也能給我們帶來一些新的開發模式

Controller 和 Signal

下面實例化了一個AbortController,它的signal屬性就是一個AbortSignal

const controller = new AbortController();
const {
     signal } = controller;
  • controller 可通過controller.abort()去終止它對應的signal
  • signal本身是不能被直接終止的。可以將它傳遞給一些函數調用如 fetch 或者直接監聽signal的狀態變化(可以通過signal.aborted查看signal的狀態或者監聽它的abort事件)

實際使用

普通對象中的終止

一些舊的 DOM api 是不支持AbortSignal。例如WebScocket只提供了一個close方法當我們無需使用時進行關閉。如果要使用AbortSignal則可以類似以下的封裝

function abortableSocket(url, signal) {
    
  const w = new WebSocket(url);

  if (signal.aborted) {
    
    w.close();  // signal已經終中止的情况下馬上關閉websocket
  }
  signal.addEventListener('abort', () => w.close());

  return w;
}

這個使用也很簡單,但是需要注意的是如果signal已經終止的情况下是不會觸發abort事件,需要我們先進行一個判斷是否signal已經終止

移除事件監聽

我們經常需要在 js 中處理 dom 的監聽和卸載工作。但是下面的例子由於事件監聽和卸載傳入的函數不是同一個引用時不會生效的

window.addEventListener('resize', () => doSomething());

// 不會生效
window.removeEventListener('resize', () => doSomething());

因此我們經常需要一些額外的代碼去維護這個回調函數的引用的一致性。而有了AbortSignal之後我們就可以有一種的新的方式去實現

const controller = new AbortController();
const {
     signal } = controller;

window.addEventListener('resize', () => doSomething(), {
     signal });

controller.abort();

因為addEventListener也能接收signal屬性的。我們最後只需要調用controller.abort(),這個controllersignal傳遞的相關事件監聽都會被自動相應卸載了

構造器模式

在 JavaScript 中我們可能需要在對象中管理非常複雜的生命周期,如WebSocket。我們需要執行開啟然後執行一系列邏輯後終止。可能我們會寫以下代碼

const someObject = new SomeObject();
someObject.start();

// 執行一些操作後
someObject.stop();

也可以通過AbortSignal進行實現

const controller = new AbortController();
const {
     signal } = controller;

const someObject = new someObject(signal);

// 執行一些操作後
controller.abort();
  1. 這能非常清晰地錶示這個對象只能被執行一次,只能從開始到結束,而不能反過來。如果它終止了後想再次使用則需要再次創建一個對象

  2. 可以在很多地方共享一個signal。我們無需持有多個SomeObject的實例。只需要調用controller.abort(),這些SomeObject的實例都能被終止掉

  3. 如果SomeObject內部也有調用像fetch之類的內部 api 只需要把這個signal繼續傳遞,則fetch也能被一起終止掉

如下是一個例子。展示了兩種 signal 的用法。傳遞給內置 apifetch和檢查signal狀態執行一些操作

export class SomeObject {
    
  constructor(signal) {
    
    this.signal = signal;

    // 執行一些操作例如發請求
    const p = fetch('/json', {
     signal });
  }

  doComplexOperation() {
    
    if (this.signal.aborted) {
    
      throw new Error(`thing stopped`);
    }
    for (let i = 0; i < 1_000_000; ++i) {
    
      // 執行複雜操作
    }
  }
}
react hook 中的异步調用

我們通常會在useEffect中進行一些异步 api 調用。借助signal可以在下一次useEffect重新調用 api 的時候將前一次的調用終止

function FooComponent({
      something }) {
    
  useEffect(() => {
    
    const controller = new AbortController();
    const {
     signal } = controller;

    const p = (async () => {
    
      const j = await fetch(url + something, {
     signal });
    })();

    return () => controller.abort();
  }, [something]);

  return <>...<>;
}

也可以封裝一個useEffectAsync的 hook

function useEffectAsync(cb,dependence) {
    
   const controller = new AbortController();
   const {
     signal } = controller;
   useEffect(() => {
    
     cb(signal);
     return () => controller.abort();
   },dependence)
}

一些有用的 AbortSignal 方法

這些方法當前有可能還沒有實現

function abortTimeout(ms) {
    
  const controller = new AbortController();
  setTimeout(() => controller.abort(), ms);
  return controller.signal;
}
  • AbortSignal.any(signals):創建一個AbortSignal,如果傳入的任一signal終止了,這個返回的signal也會被終止
function abortAny(signals) {
    
  const controller = new AbortController();
  signals.forEach((signal) => {
    
    if (signal.aborted) {
    
      controller.abort();
    } else {
    
      signal.addEventListener('abort', () => controller.abort());
    }
  });
  return controller.signal;
}
  • AbortSignal.throwIfAborted():如果signal本身已經終止了,調用該方法會拋出執行abort(reason)時指定的 reason 异常;否則只會靜默執行
  if (signal.aborted) {
    
    throw new Error(...);
  }
  // becomes
  signal.throwIfAborted();

這個方法目前不太容易 polyfill,但是可通過下面的工具函數實現

function throwIfSignalAborted(signal) {
  if (signal.aborted) {
    throw new Error(...);
  }
}

參考

https://whistlr.info/2022/abortcontroller-is-your-friend/

原网站

版权声明
本文为[flytam]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/177/202206261513333037.html