AbortController

Pozwala przerwać operację asynchroniczną, np. zapytanie fetch(), i odpiąć wiele obsług zdarzeń naraz jednym wywołaniem abort().

Zwraca
Nowy obiekt AbortController z właściwością signal.
Na tej stronie

Przykład

#
JavaScript
const controller = new AbortController()
const { signal } = controller

signal.addEventListener('abort', () => console.log('Przerwano:', signal.reason.name))

const request = fetch('data:application/json,{"courses":12}', { signal })
controller.abort()

try {
  await request
} catch (error) {
  console.log('fetch odrzucony:', error.name)
}

console.log(signal.aborted)
Konsola
Przerwano: AbortError
fetch odrzucony: AbortError
true

Zdarzenie abort pojawia się od razu w trakcie controller.abort(). Obietnica zapytania zostaje odrzucona błędem AbortError, który łapie blok catch.

Definicja i zastosowanie

#

AbortController składa się z dwóch części: kontrolera z metodą abort() i sygnału (controller.signal), który przekazujesz do operacji, np. fetch(url, { signal }). Wywołanie abort() informuje wszystkie operacje z tym sygnałem, że mają się zakończyć. Przerwane zapytanie fetch() odrzuca obietnicę błędem o nazwie AbortError.

Sygnał ma właściwość aborted (czy przerwano), reason (powód przerwania: domyślnie błąd AbortError, ale do abort() możesz przekazać własny) i zgłasza zdarzenie abort. Metoda signal.throwIfAborted() rzuca powód przerwania, jeśli sygnał jest już przerwany, co przydaje się w dłuższych funkcjach. Jeden kontroler służy tylko raz: przerwany sygnał zostaje przerwany na zawsze, więc do kolejnej operacji utwórz nowy kontroler.

Ten sam sygnał przekażesz też do addEventListener() w opcji signal. Po abort() wszystkie takie obsługi zostaną odpięte, bez pamiętania każdej funkcji osobno. Gotowe sygnały tworzą też metody statyczne: AbortSignal.timeout(ms) przerywa operację po upływie czasu (z błędem TimeoutError), a nowsza AbortSignal.any([...]) łączy kilka sygnałów w jeden.

Składnia

#
Składnia
const controller = new AbortController()
controller.signal
controller.abort()
controller.abort(reason)

signal.aborted
signal.reason
signal.throwIfAborted()
AbortSignal.timeout(ms)

Parametry

#
  • reason

    opcjonalny, dowolna wartość

    Powód przerwania przekazany do abort(). Trafi do signal.reason i do błędu odrzucającego obietnicę. Domyślnie to błąd DOMException o nazwie AbortError.

Więcej przykładów

#
Anulowanie poprzedniego wyszukiwania
JavaScript
let controller

async function search(query) {
  controller?.abort()
  controller = new AbortController()

  try {
    const response = await fetch(`data:application/json,{"query":"${query}"}`, {
      signal: controller.signal
    })
    const data = await response.json()
    console.log('Wyniki dla:', data.query)
  } catch (error) {
    if (error.name !== 'AbortError') throw error
    console.log('Anulowano:', query)
  }
}

search('ja')
search('jav')
search('java')
Konsola
Anulowano: ja
Anulowano: jav
Wyniki dla: java

Każde nowe wyszukiwanie przerywa poprzednie zapytanie, więc wynik dostajemy tylko dla ostatniego tekstu. Tak działa wyszukiwarka, która szuka już w trakcie pisania.

Odpięcie wielu obsług zdarzeń naraz
JavaScript
const button = document.querySelector('#start')
const controller = new AbortController()
const { signal } = controller

button.addEventListener('click', () => console.log('Start quizu'), { signal })
button.addEventListener('click', () => console.log('Licznik czasu włączony'), { signal })
document.addEventListener('keydown', () => console.log('Wciśnięto klawisz'), { signal })

button.click()
controller.abort()
button.click()
console.log('Obsługi odpięte:', signal.aborted)
Konsola
Start quizu
Licznik czasu włączony
Obsługi odpięte: true

Po controller.abort() drugie kliknięcie niczego nie wypisuje. Wszystkie trzy obsługi zostały odpięte jednym wywołaniem.

Własna funkcja, którą można przerwać
JavaScript
function wait(ms, signal) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(resolve, ms)
    signal.addEventListener(
      'abort',
      () => {
        clearTimeout(timer)
        reject(signal.reason)
      },
      { once: true }
    )
  })
}

const controller = new AbortController()
setTimeout(() => controller.abort(new Error('Przekroczono limit czasu na odpowiedź')), 200)

try {
  await wait(1000, controller.signal)
  console.log('Odpowiedź na czas')
} catch (error) {
  console.log(error.message)
}
Konsola
Przekroczono limit czasu na odpowiedź

Funkcja wait() nasłuchuje zdarzenia abort na sygnale i odrzuca obietnicę z powodem przekazanym do abort(). W ten sam sposób możesz dodać przerywanie do własnych operacji asynchronicznych.

Obsługa przeglądarek

#

Szeroko dostępne · od 2019 roku

Działa we wszystkich nowoczesnych przeglądarkach, także na telefonach. Możesz używać bez obaw.

  • Chrome
  • Edge
  • Firefox
  • Safari

Dobre praktyki

#
  • Do każdej nowej operacji twórz nowy AbortController. Przerwany sygnał zostaje przerwany na zawsze i od razu przerwie każde kolejne zapytanie.
  • W bloku catch odróżniaj przerwanie od prawdziwego błędu przez error.name === "AbortError". Anulowanie przez użytkownika zwykle nie wymaga komunikatu o błędzie.
  • Do prostego limitu czasu wystarczy fetch(url, { signal: AbortSignal.timeout(5000) }), bez tworzenia kontrolera.

Powiązane hasła

#

Widzisz błąd albo brakuje przykładu? Napisz do nas.