Apprenez à utiliser l'AbortController de JavaScript pour annuler efficacement les opérations asynchrones (fetch, timers, etc.) pour un code plus propre et performant.
JavaScript AbortController : Maîtriser l'annulation des opérations asynchrones
Dans le développement web moderne, les opérations asynchrones sont omniprésentes. Récupérer des données d'API, définir des minuteries et gérer les interactions utilisateur impliquent souvent du code qui s'exécute indépendamment et potentiellement pendant une durée prolongée. Cependant, il existe des scénarios où vous devez annuler ces opérations avant qu'elles ne soient terminées. C'est là que l'interface AbortController
en JavaScript vient à la rescousse. Elle fournit un moyen propre et efficace de signaler des demandes d'annulation aux opérations DOM et à d'autres tâches asynchrones.
Comprendre le besoin d'annulation
Avant de plonger dans les détails techniques, comprenons pourquoi l'annulation des opérations asynchrones est importante. Considérez ces scénarios courants :
- Navigation utilisateur : Un utilisateur initie une requête de recherche, déclenchant une requête API. S'il navigue rapidement vers une autre page avant que la requête ne soit terminée, la requête d'origine devient non pertinente et doit être annulée pour éviter un trafic réseau inutile et des effets secondaires potentiels.
- Gestion des délais d'attente : Vous définissez un délai d'attente pour une opération asynchrone. Si l'opération se termine avant l'expiration du délai, vous devez annuler le délai pour éviter une exécution de code redondante.
- Démontage de composant : Dans les frameworks front-end comme React ou Vue.js, les composants effectuent souvent des requêtes asynchrones. Lorsqu'un composant est démonté, toute requête en cours associée à ce composant doit être annulée pour éviter les fuites de mémoire et les erreurs causées par la mise à jour de composants démontés.
- Contraintes de ressources : Dans les environnements aux ressources limitées (par exemple, appareils mobiles, systèmes embarqués), l'annulation des opérations inutiles peut libérer des ressources précieuses et améliorer les performances. Par exemple, annuler le téléchargement d'une grande image si l'utilisateur fait défiler cette section de la page.
Introduction à AbortController et AbortSignal
L'interface AbortController
est conçue pour résoudre le problème de l'annulation des opérations asynchrones. Elle se compose de deux éléments clés :
- AbortController : Cet objet gère le signal d'annulation. Il possède une seule méthode,
abort()
, qui est utilisée pour signaler une demande d'annulation. - AbortSignal : Cet objet représente le signal indiquant qu'une opération doit être abandonnée. Il est associé à un
AbortController
et est passé à l'opération asynchrone qui doit être annulable.
Utilisation de base : Annuler les requêtes Fetch
Commençons par un exemple simple d'annulation d'une requête fetch
:
const controller = new AbortController();
const signal = controller.signal;
fetch('https://api.example.com/data', { signal })
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Data:', data);
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Fetch error:', error);
}
});
// Pour annuler la requête fetch :
controller.abort();
Explication :
- Nous créons une instance
AbortController
. - Nous obtenons l'
AbortSignal
associé à partir ducontroller
. - Nous passons le
signal
aux optionsfetch
. - Si nous devons annuler la requête, nous appelons
controller.abort()
. - Dans le bloc
.catch()
, nous vérifions si l'erreur est unAbortError
. Si c'est le cas, nous savons que la requête a été annulée.
Gestion de AbortError
Lorsque controller.abort()
est appelé, la requête fetch
sera rejetée avec un AbortError
. Il est crucial de gérer cette erreur de manière appropriée dans votre code. Ne pas le faire peut entraîner des rejets de promesse non gérés et un comportement inattendu.
Voici un exemple plus robuste avec gestion des erreurs :
const controller = new AbortController();
const signal = controller.signal;
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data', { signal });
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
console.log('Data:', data);
return data;
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
return null; // Ou lancez l'erreur pour être traitée plus loin
} else {
console.error('Fetch error:', error);
throw error; // Relancez l'erreur pour être traitée plus loin
}
}
}
fetchData();
// Pour annuler la requête fetch :
controller.abort();
Bonnes pratiques pour la gestion de AbortError :
- Vérifiez le nom de l'erreur : Vérifiez toujours si
error.name === 'AbortError'
pour vous assurer que vous traitez le bon type d'erreur. - Retournez une valeur par défaut ou relancez : Selon la logique de votre application, vous pourriez vouloir retourner une valeur par défaut (par exemple,
null
) ou relancer l'erreur pour qu'elle soit traitée plus haut dans la pile d'appels. - Nettoyez les ressources : Si l'opération asynchrone a alloué des ressources (par exemple, des minuteurs, des écouteurs d'événements), nettoyez-les dans le gestionnaire d'
AbortError
.
Annuler les minuteurs avec AbortSignal
L'AbortSignal
peut également être utilisé pour annuler les minuteurs créés avec setTimeout
ou setInterval
. Cela nécessite un peu plus de travail manuel, car les fonctions de minuterie intégrées ne prennent pas directement en charge AbortSignal
. Vous devez créer une fonction personnalisée qui écoute le signal d'abandon et efface le minuteur lorsqu'il est déclenché.
function cancellableTimeout(callback, delay, signal) {
let timeoutId;
const timeoutPromise = new Promise((resolve, reject) => {
timeoutId = setTimeout(() => {
resolve(callback());
}, delay);
signal.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject(new Error('Timeout Aborted'));
});
});
return timeoutPromise;
}
const controller = new AbortController();
const signal = controller.signal;
cancellableTimeout(() => {
console.log('Timeout executed');
}, 2000, signal)
.then(() => console.log("Timeout finished successfully"))
.catch(err => console.log(err));
// Pour annuler le délai d'attente :
controller.abort();
Explication :
- La fonction
cancellableTimeout
prend un callback, un délai et unAbortSignal
comme arguments. - Elle configure un
setTimeout
et stocke l'ID du minuteur. - Elle ajoute un écouteur d'événements à l'
AbortSignal
qui écoute l'événementabort
. - Lorsque l'événement
abort
est déclenché, l'écouteur d'événements efface le minuteur et rejette la promesse.
Annuler les écouteurs d'événements
Similaire aux minuteurs, vous pouvez utiliser AbortSignal
pour annuler les écouteurs d'événements. C'est particulièrement utile lorsque vous souhaitez supprimer les écouteurs d'événements associés à un composant qui est en cours de démontage.
const controller = new AbortController();
const signal = controller.signal;
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('Button clicked!');
}, { signal });
// Pour annuler l'écouteur d'événements :
controller.abort();
Explication :
- Nous passons le
signal
comme option à la méthodeaddEventListener
. - Lorsque
controller.abort()
est appelé, l'écouteur d'événements sera automatiquement supprimé.
AbortController dans les composants React
Dans React, vous pouvez utiliser AbortController
pour annuler les opérations asynchrones lors du démontage d'un composant. Ceci est essentiel pour éviter les fuites de mémoire et les erreurs causées par la mise à jour de composants non montés. Voici un exemple utilisant le hook useEffect
:
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data', { signal });
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
setData(data);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Fetch error:', error);
}
}
}
fetchData();
return () => {
controller.abort(); // Annule la requête fetch lorsque le composant est démonté
};
}, []); // Le tableau de dépendances vide garantit que cet effet ne s'exécute qu'une seule fois au montage
return (
{data ? (
Data: {JSON.stringify(data)}
) : (
Loading...
)}
);
}
export default MyComponent;
Explication :
- Nous créons un
AbortController
dans le hookuseEffect
. - Nous passons le
signal
à la requêtefetch
. - Nous retournons une fonction de nettoyage du hook
useEffect
. Cette fonction sera appelée lors du démontage du composant. - À l'intérieur de la fonction de nettoyage, nous appelons
controller.abort()
pour annuler la requête fetch.
Cas d'utilisation avancés
Chaînage de AbortSignals
Parfois, vous voudrez peut-être chaîner plusieurs AbortSignal
ensemble. Par exemple, vous pourriez avoir un composant parent qui doit annuler les opérations dans ses composants enfants. Vous pouvez y parvenir en créant un nouvel AbortController
et en passant son signal aux composants parent et enfant.
Utilisation d'AbortController avec des bibliothèques tierces
Si vous utilisez une bibliothèque tierce qui ne prend pas directement en charge AbortSignal
, vous devrez peut-être adapter votre code pour fonctionner avec le mécanisme d'annulation de la bibliothèque. Cela pourrait impliquer d'encapsuler les fonctions asynchrones de la bibliothèque dans vos propres fonctions qui gèrent l'AbortSignal
.
Avantages de l'utilisation d'AbortController
- Amélioration des performances : L'annulation des opérations inutiles peut réduire le trafic réseau, l'utilisation du CPU et la consommation de mémoire, ce qui améliore les performances, en particulier sur les appareils aux ressources limitées.
- Code plus propre :
AbortController
fournit un moyen standardisé et élégant de gérer l'annulation, rendant votre code plus lisible et maintenable. - Prévention des fuites de mémoire : L'annulation des opérations asynchrones associées aux composants démontés évite les fuites de mémoire et les erreurs causées par la mise à jour de composants non montés.
- Meilleure expérience utilisateur : L'annulation des requêtes non pertinentes peut améliorer l'expérience utilisateur en empêchant l'affichage d'informations obsolètes et en réduisant la latence perçue.
Compatibilité des navigateurs
AbortController
est largement pris en charge dans les navigateurs modernes, y compris Chrome, Firefox, Safari et Edge. Vous pouvez consulter le tableau de compatibilité sur les MDN Web Docs pour obtenir les informations les plus récentes.
Polyfills
Pour les anciens navigateurs qui ne prennent pas en charge nativement AbortController
, vous pouvez utiliser un polyfill. Un polyfill est un morceau de code qui fournit la fonctionnalité d'une nouvelle fonctionnalité dans les anciens navigateurs. Il existe plusieurs polyfills AbortController
disponibles en ligne.
Conclusion
L'interface AbortController
est un outil puissant pour gérer les opérations asynchrones en JavaScript. En utilisant AbortController
, vous pouvez écrire du code plus propre, plus performant et plus robuste qui gère l'annulation avec élégance. Que vous récupériez des données d'API, que vous définissiez des minuteurs ou que vous gériez des écouteurs d'événements, AbortController
peut vous aider à améliorer la qualité globale de vos applications web.