Les callbacks en JavaScript
En JavaScript, les callbacks sont un concept essentiel qui permet aux développeurs de gérer efficacement les opérations asynchrones.
Un callback est simplement une fonction que vous passez en argument à une autre fonction, afin que la fonction réceptrice puisse l'appeler ultérieurement. Puisque les fonctions sont des valeurs de première classe en JavaScript — elles peuvent être stockées dans des variables, transmises et retournées — n'importe quelle fonction peut accepter une autre fonction en paramètre. Cette seule idée est à l'origine de tout, des méthodes de tableau comme map aux minuteries et aux requêtes réseau.
Ce chapitre explique ce que sont les callbacks, la différence entre les callbacks synchrones et asynchrones, la convention error-first, le problème du callback hell qu'ils engendrent, et comment les Promises et async/await le résolvent.
Un premier callback
La fonction que vous passez est le callback ; la fonction qui la reçoit et l'invoque est la fonction d'ordre supérieur. Ici, un callback s'exécute une fois qu'un travail est terminé :
finishTask est passé à completeTask et invoqué à l'intérieur. Notez que vous passez le nom de la fonction (finishTask) sans parenthèses — les ajouter l'appellerait immédiatement et transmettrait sa valeur de retour à la place.
Callbacks synchrones et asynchrones
Tous les callbacks ne concernent pas l'attente. Il existe deux types distincts, et les confondre est une source fréquente de bugs.
Callbacks synchrones
Un callback synchrone s'exécute immédiatement, dans l'ordre, avant que la fonction externe ne retourne. Les méthodes de tableau en sont l'exemple classique :
Ici, transform est appelé et terminé pour chaque élément avant que map ne retourne. Les méthodes intégrées telles que Array.prototype.map, filter, forEach et sort prennent toutes des callbacks synchrones.
Callbacks asynchrones
Un callback asynchrone est confié à une opération qui se termine plus tard — une minuterie, une lecture de fichier ou une requête réseau. La fonction externe retourne immédiatement, et le callback se déclenche une fois le résultat disponible. JavaScript planifie ces opérations via la boucle d'événements, qui met les callbacks en file d'attente et les exécute lorsque la pile d'appels est vide.
Même avec un délai de 0 ms, le callback s'exécute après le code synchrone environnant. C'est la caractéristique définissante d'un callback async : il ne peut pas retourner une valeur de manière habituelle, donc la seule façon d'utiliser le résultat est de placer votre code de continuation à l'intérieur du callback. Pour comprendre exactement quand ceux-ci s'exécutent, consultez JavaScript : Event Loop.
La convention error-first
Les callbacks async ne peuvent pas throw vers le code qui a lancé l'opération — au moment où ils s'exécutent, ce code est déjà retourné depuis longtemps. La communauté a adopté une convention : passer l'erreur comme premier argument, et le résultat comme second. Le callback vérifie toujours l'erreur en premier.
Lorsque tout réussit, le slot d'erreur est null. Cette signature (err, result) est la norme dans les API Node.js (fs.readFile, dns.lookup et bien d'autres).
Callback hell : la pyramide de la doom
Les callbacks fonctionnent bien pour une seule opération. Les problèmes commencent lorsqu'une étape async dépend du résultat de la précédente. Chaque étape s'imbrique dans la précédente, et l'indentation progresse vers la droite — c'est la fameuse pyramide de la doom :
Avec deux étapes, c'est encore lisible. Ajoutez-en une troisième et une quatrième — et répétez la vérification if (err) à chaque niveau — et le code devient difficile à suivre, à gérer en termes d'erreurs, et à modifier. C'est le callback hell, et c'est la principale raison pour laquelle les Promises ont été introduites.
Bonnes pratiques pour l'utilisation des callbacks
Bien que les callbacks soient puissants, leur utilisation excessive ou incorrecte peut mener au « callback hell », où le code s'imbrique trop profondément et devient difficile à lire et à maintenir. Voici quelques bonnes pratiques pour garder votre code propre :
- Modularisez votre code : Décomposez vos fonctions callback en fonctions plus petites et réutilisables. Cette approche améliore non seulement la lisibilité, mais aussi la maintenance du code.
- Gérez les erreurs avec élégance : Gérez toujours les erreurs dans vos callbacks. Cette pratique évite les plantages et les comportements indésirables en production.
- Évitez l'imbrication profonde : Essayez d'aplatir vos structures de callbacks autant que possible. Des outils comme async/await ou les Promises peuvent aider à gérer les opérations asynchrones de manière plus propre.
- Faites attention aux fermetures dans les boucles : Lors de la définition de callbacks à l'intérieur de boucles, utilisez
letouconstpour les variables de boucle afin d'éviter les bugs liés aux fermetures où tous les callbacks capturent la valeur finale de la boucle.
Aller au-delà des callbacks : Promises et Async/Await
Bien que les callbacks soient une partie fondamentale de JavaScript, le JavaScript moderne offre des façons plus abstraites de gérer le code asynchrone, telles que les Promises et async/await.
Utiliser les Promises
Une Promise représente une valeur qui peut être disponible maintenant, dans le futur, ou jamais. Au lieu de l'imbrication, les étapes dépendantes sont enchaînées les unes après les autres, ce qui aplatit la pyramide et centralise la gestion des erreurs dans un seul .catch. Commencez avec JavaScript : Promises, puis voyez comment les étapes se connectent dans JavaScript : Chaînage de Promises.
Si vous disposez d'une ancienne fonction basée sur des callbacks (comme les exemples divide ou getUser ci-dessus), vous pouvez l'envelopper pour qu'elle retourne une Promise — une technique appelée promisification. Voir JavaScript : Promisification.
Async/Await : une approche plus claire
La syntaxe async/await vous permet d'écrire du code asynchrone qui se lit comme du code synchrone. Elle est construite au-dessus des Promises et est plus intuitive que les patterns traditionnels de callbacks. Lisez JavaScript : Async/Await pour voir comment vous pouvez utiliser async/await à la place des callbacks.
Conclusion
Comprendre et utiliser efficacement les callbacks est crucial pour les développeurs JavaScript. En suivant les bonnes pratiques et en utilisant des fonctionnalités modernes comme les Promises et async/await, vous pouvez écrire du code plus propre et plus maintenable. Maîtrisez ces concepts pour améliorer vos compétences en programmation JavaScript et créer des applications plus efficaces.