Menu
Français

Callbacks en JavaScript : fonctions en argument et callback hell

Comprendre les callbacks en JavaScript : passer une fonction en argument, le pattern error-first et pourquoi l'imbrication pousse vers les promesses.

Une callback, c'est une fonction qu'on confie à une autre fonction

En JavaScript, les fonctions sont des valeurs comme les autres. On peut les stocker dans des variables, les glisser dans des tableaux et — ce qui nous intéresse ici — les passer en argument. Quand tu passes une fonction à une autre fonction pour qu'elle l'appelle plus tard, la fonction en question s'appelle une callback (ou fonction de rappel).

index.js
Output
Click Run to see the output here.

greet ne sait pas (et ne se soucie pas de) ce que fait formatter. Elle l'appelle simplement avec un nom et utilise ce qu'elle reçoit en retour. Tu choisis le comportement en passant différents callbacks. Et c'est précisément pour cette flexibilité que les callbacks existent.

Les callbacks synchrones s'exécutent immédiatement

Tous les callbacks ne sont pas asynchrones. Beaucoup de méthodes de tableau que tu utilises déjà reposent sur des callbacks, et ceux-ci s'exécutent de façon synchrone — avant même que l'appel extérieur ne rende la main :

index.js
Output
Click Run to see the output here.

map, filter et reduce prennent tous une fonction callback et l'exécutent une fois par élément, immédiatement. Au moment où map retourne, chaque appel au callback a déjà eu lieu. Rien n'est mis en file d'attente pour plus tard.

C'est un simple schéma de fonction d'ordre supérieur — « voici du travail, voici comment le faire, renvoie-moi le résultat ». Pas d'event loop dans l'histoire.

Les callbacks asynchrones s'exécutent plus tard

Quand on parle de « callback » en JavaScript, on pense généralement à la version asynchrone. Tu passes une fonction à une API qui prend du temps — un timer, une requête réseau, une lecture de fichier — et cette API rappelle ta fonction une fois le travail terminé.

index.js
Output
Click Run to see the output here.

L'ordre d'affichage sera : avant, après, puis minuteur déclenché une seconde plus tard. setTimeout ne met pas votre programme en pause. Il confie le callback au runtime, rend la main immédiatement, et le reste du script continue de s'exécuter. Une seconde plus tard, la boucle d'événements récupère le callback et le déclenche.

Ce schéma « je rends la main maintenant, je te rappelle plus tard » constitue le modèle mental de toutes les API de callback asynchrone en JavaScript, de addEventListener aux anciennes API fichier de Node.js.

La convention error-first (Node.js)

Avant l'arrivée des promesses, Node.js avait standardisé une forme bien précise de callback : le premier argument correspond à une erreur (ou null), et les suivants contiennent le résultat effectif. Vous tomberez encore dessus dans du code plus ancien et dans certaines bibliothèques.

index.js
Output
Click Run to see the output here.

L'appelant vérifie d'abord err et sort immédiatement si cette valeur est truthy. Ce n'est qu'ensuite qu'il fait confiance au résultat. C'est une convention — pas imposée par le langage — mais dès que tu croises la signature (err, result) => ..., tu la reconnaîtras partout.

Le callback hell en JavaScript

Les ennuis commencent quand une étape asynchrone dépend du résultat d'une autre. Chaque fonction callback doit s'imbriquer dans la précédente, et tu te retrouves avec un escalier qui part en biais vers la marge :

index.js
Output
Click Run to see the output here.

C'est la fameuse « pyramide de la mort », aussi appelée callback hell. Plusieurs choses la rendent pénible :

  • Le flux d'exécution part en zigzag au lieu de se lire de haut en bas.
  • Chaque niveau répète le même if (err) return ... en boucle.
  • Si un callback lève une exception, elle ne remonte pas aux callbacks extérieurs — il faut gérer les erreurs à chaque étage.
  • Le moindre refactoring oblige à réindenter tout le bloc.

On peut aplatir un peu tout ça en extrayant des fonctions nommées, mais le vrai problème reste entier : composer de l'asynchrone avec des callbacks bruts, c'est maladroit. C'est précisément pour ça que les promesses ont été inventées.

Deux pièges à connaître

N'appelez pas le callback par accident. Quand vous passez un callback, vous passez la fonction elle-même — pas le résultat de son appel.

index.js
Output
Click Run to see the output here.

Attention au this. Si ton callback est une fonction classique qui utilise this, la valeur de this dépend de la façon dont la fonction est appelée, pas de l'endroit où elle a été définie. Les fonctions fléchées contournent le problème en héritant du this du scope englobant :

index.js
Output
Click Run to see the output here.

Les fonctions fléchées sont le choix par défaut pour les callbacks inline, précisément pour cette raison.

Callback vs Promise en JavaScript

Les callbacks restent très présents dans les API synchrones (map, forEach, sort), les écouteurs d'événements (element.addEventListener("click", ...)) et les hooks bas niveau du runtime. En revanche, pour l'asynchrone qui produit un seul résultat, l'écosystème est passé presque entièrement aux promesses.

Comparaison rapide :

  • Callbacks — directs, minimalistes, mais ils se composent mal. La gestion d'erreur est manuelle à chaque étape.
  • Promesses — une valeur qui représente un résultat futur. On les enchaîne avec .then(), on capture les erreurs une seule fois avec .catch(), et la pyramide s'aplatit.

Comprendre les callbacks reste indispensable : les promesses reposent dessus, et on en trouve partout dans le code événementiel. Mais on n'écrit quasiment plus de nouvelles API asynchrones à base de callbacks bruts.

La suite : les promesses

Les promesses reprennent l'idée du « fais ceci quand ce sera prêt » et l'emballent dans un objet que l'on peut passer, chaîner et composer. C'est le sujet de la page suivante — et la porte d'entrée vers async/await, la manière dont le JavaScript moderne gère l'asynchrone au quotidien.

Questions fréquentes

C'est quoi une fonction callback en JavaScript ?

Un callback, c'est une fonction que tu passes en argument à une autre fonction, pour que cette dernière puisse l'appeler plus tard. Par exemple, setTimeout(() => console.log('hi'), 1000) passe une arrow function comme callback : setTimeout la garde de côté et l'exécute quand le timer se déclenche. Les callbacks, c'est la façon originelle dont JavaScript gère le « fais ça quand ce sera prêt ».

Quelle est la différence entre un callback synchrone et asynchrone ?

Un callback synchrone s'exécute tout de suite, pendant l'appel qui l'a reçu — [1, 2, 3].map(x => x * 2) invoque le callback trois fois avant que map ne retourne quoi que ce soit. Un callback asynchrone, lui, est stocké et appelé plus tard, après un événement : c'est le cas de setTimeout, fs.readFile ou des écouteurs d'événements DOM. L'avantage : les callbacks async ne bloquent pas le reste du code.

C'est quoi le callback hell et comment l'éviter ?

Le callback hell, c'est cette fameuse pyramide qui apparaît quand plusieurs callbacks asynchrones dépendent les uns des autres et finissent imbriqués sur plusieurs niveaux. Résultat : le flot d'exécution et la gestion des erreurs deviennent illisibles. La solution, c'est d'enchaîner les promesses avec .then(), ou encore mieux, d'utiliser async/await — les deux aplatissent la pyramide pour redonner un code lisible.

Apprendre à coder avec Coddy

COMMENCER