AbortController

Permet d’interrompre une opération asynchrone, par exemple une requête fetch(), et de détacher plusieurs gestionnaires d’événements en un seul appel à abort().

Renvoie
Un nouvel objet AbortController avec une propriété signal.
Sur cette page

Exemple

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

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

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

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

console.log(signal.aborted)
Console
Interrompu : AbortError
fetch rejeté : AbortError
true

L’événement abort apparaît immédiatement pendant controller.abort(). La promesse de la requête est rejetée avec une erreur AbortError, que le bloc catch intercepte.

Définition et utilisation

#

AbortController se compose de deux parties : un contrôleur avec une méthode abort(), et un signal (controller.signal) que vous transmettez à une opération, par exemple fetch(url, { signal }). Appeler abort() indique à toutes les opérations utilisant ce signal qu’elles doivent s’arrêter. Une requête fetch() interrompue rejette sa promesse avec une erreur nommée AbortError.

Le signal a une propriété aborted (si l’opération a été interrompue), reason (la raison de l’interruption : par défaut l’erreur AbortError, mais vous pouvez transmettre la vôtre à abort()) et déclenche un événement abort. La méthode signal.throwIfAborted() lève la raison de l’interruption si le signal est déjà interrompu, ce qui est utile dans des fonctions plus longues. Un contrôleur ne sert qu’une seule fois : un signal interrompu le reste pour toujours, donc créez un nouveau contrôleur pour l’opération suivante.

Vous pouvez transmettre ce même signal à addEventListener(), dans l’option signal. Après abort(), tous ces gestionnaires sont détachés, sans avoir à mémoriser chaque fonction séparément. Des méthodes statiques créent aussi des signaux tout prêts : AbortSignal.timeout(ms) interrompt une opération au bout d’un certain temps (avec une erreur TimeoutError), et la plus récente AbortSignal.any([...]) combine plusieurs signaux en un seul.

Syntaxe

#
Syntaxe
const controller = new AbortController()
controller.signal
controller.abort()
controller.abort(reason)

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

Paramètres

#
  • reason

    valeur quelconque, optionnel

    La raison de l’interruption, transmise à abort(). Elle atterrit dans signal.reason et dans l’erreur qui rejette la promesse. Par défaut, c’est une erreur DOMException nommée AbortError.

Plus d’exemples

#
Annuler la recherche précédente
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('Résultats pour :', data.query)
  } catch (error) {
    if (error.name !== 'AbortError') throw error
    console.log('Annulé :', query)
  }
}

search('ja')
search('jav')
search('java')
Console
Annulé : ja
Annulé : jav
Résultats pour : java

Chaque nouvelle recherche interrompt la requête précédente, donc on n’obtient un résultat que pour le dernier texte saisi. C’est ainsi que fonctionne un champ de recherche qui cherche déjà pendant la frappe.

Détacher plusieurs gestionnaires d’événements à la fois
JavaScript
const button = document.querySelector('#start')
const controller = new AbortController()
const { signal } = controller

button.addEventListener('click', () => console.log('Quiz démarré'), { signal })
button.addEventListener('click', () => console.log('Minuteur activé'), { signal })
document.addEventListener('keydown', () => console.log('Touche appuyée'), { signal })

button.click()
controller.abort()
button.click()
console.log('Gestionnaires détachés :', signal.aborted)
Console
Quiz démarré
Minuteur activé
Gestionnaires détachés : true

Après controller.abort(), le second clic n’affiche plus rien. Les trois gestionnaires ont été détachés par un seul appel.

Une fonction à vous, qu’on peut interrompre
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('Délai de réponse dépassé')), 200)

try {
  await wait(1000, controller.signal)
  console.log('Réponse à temps')
} catch (error) {
  console.log(error.message)
}
Console
Délai de réponse dépassé

La fonction wait() écoute l’événement abort sur le signal et rejette la promesse avec la raison transmise à abort(). Vous pouvez ajouter de la même façon une interruption à vos propres opérations asynchrones.

Compatibilité des navigateurs

#

Largement disponible · depuis 2019

Fonctionne dans tous les navigateurs modernes, y compris sur mobile. Vous pouvez l’utiliser sans crainte.

  • Chrome
  • Edge
  • Firefox
  • Safari

Bonnes pratiques

#
  • Créez un nouvel AbortController pour chaque nouvelle opération. Un signal interrompu le reste pour toujours et interromprait aussitôt chaque requête suivante.
  • Dans un bloc catch, distinguez une interruption d’une vraie erreur avec error.name === "AbortError". Une annulation par l’utilisateur n’a généralement pas besoin d’un message d’erreur.
  • Pour une simple limite de temps, fetch(url, { signal: AbortSignal.timeout(5000) }) suffit, sans créer de contrôleur.

Entrées associées

#

Vous voyez une erreur ou il manque un exemple ? Écrivez-nous.