Pile d'appels en JavaScript : comment le moteur suit ce qu'il est en train de faire

La pile d'appels enregistre les fonctions en cours d'exécution pour savoir où revenir. C'est elle qu'on lit dans une trace d'erreur.
4 min de lecture
Believemy logo

JavaScript exécute une seule chose à la fois. Quand une fonction en appelle une autre, il faut bien qu'il se souvienne d'où il vient pour y revenir une fois le travail terminé.

Ce carnet de retour porte un nom : la pile d'appels. La connaître change la façon de lire un message d'erreur, et explique pourquoi une récursivité mal bornée fait tomber le programme entier.


Définition

La pile d'appels, ou call stack, est la structure dans laquelle le moteur empile les fonctions en cours. Chaque appel ajoute une entrée au sommet, chaque return la retire. Le dernier entré est toujours le premier sorti.

JAVASCRIPT
function troisieme() {
  throw new Error("quelque chose a cassé");
}
function deuxieme() { troisieme(); }
function premiere() { deuxieme(); }

try {
  premiere();
} catch (erreur) {
  console.log(erreur.stack.split("\n").slice(0, 4).join("\n"));
}

// Error: quelque chose a cassé
//     at troisieme
//     at deuxieme
//     at premiere

La trace se lit de haut en bas comme un chemin inverse : la première ligne est l'endroit exact de l'incident, les suivantes disent qui a demandé quoi pour en arriver là.

Pile d'appels : empiler, dépiler, déborderEn haut, trois appels imbriqués : premiere() appelle deuxieme() qui appelle troisieme(). Le dernier cadre entré est le premier sorti. En bas, la même fonction appelée sans condition d'arrêt remplit la pile jusqu'à la limite du moteur, autour de dix mille appels ; l'appel suivant n'est jamais empilé et le programme s'arrête sur une RangeError.Trois appels imbriquéstroisieme()3e entré, 1er sortideuxieme()2e entré, 2e sortipremiere()1er entré, 3e sortiun appel empile, un return dépileSans condition d'arrêtdescendre()RangeErrorjamais empilélimite du moteur≈ 10 000 appelsdescendre()aucun return,rien ne dépile


Ce que la pile ne contient pas

C'est le point qui surprend le plus. Un setTimeout() ou une Promise ne restent pas sur la pile pendant leur attente : leur fonction de rappel est mise de côté et ne revient qu'une fois la pile entièrement vidée.

JAVASCRIPT
console.log("1 debut");
setTimeout(() => console.log("3 minuteur"), 0);
console.log("2 fin");

// 1 debut
// 2 fin
// 3 minuteur

Même avec un délai de zéro, le minuteur passe en dernier. C'est le travail de l'Event loop (boucle d'événements), qui ne réalimente la pile que lorsqu'elle est vide. Conséquence directe : une fonction qui bloque la pile gèle tout le reste, y compris l'affichage.


Le débordement de pile

La pile a une taille limite. La dépasser produit une erreur immédiate, presque toujours causée par une Récursivité sans condition d'arrêt.

JAVASCRIPT
let profondeur = 0;

function descendre() {
  profondeur += 1;
  descendre();
}

try {
  descendre();
} catch (erreur) {
  console.log(erreur.constructor.name); // RangeError
  console.log(profondeur > 1000);       // true
}
Bon à savoir

Un débordement n'accuse pas toujours une récursivité infinie. Un arbre très profond, ou deux fonctions qui s'appellent mutuellement, produisent la même erreur avec une logique pourtant correcte.


Questions fréquentes

Question

Quelle est la taille maximale de la pile ?

Elle n'est fixée par aucune spécification et varie selon le moteur, le navigateur et la mémoire disponible. L'ordre de grandeur tourne autour de dix mille appels imbriqués. Un code qui s'en approche a de toute façon un problème de conception : la profondeur devrait dépendre des données, jamais du hasard.


Question

Pourquoi ma trace d'erreur ne montre-t-elle que des noms illisibles ?

Parce que le code déployé est minifié : les noms de fonctions ont été raccourcis pour réduire le poids du fichier. La solution est de publier une Source map avec la version compilée, que l'outil de suivi utilise pour retrouver les noms et les numéros de ligne d'origine.


Question

Une fonction asynchrone occupe-t-elle la pile pendant son attente ?

Non. Au moment de l'await, la fonction est retirée de la pile et son état mis de côté. Elle y revient quand le résultat arrive, ce qui libère entre-temps le moteur pour tout le reste. C'est exactement ce qui permet à une page de rester réactive pendant un appel réseau.

Termes connexes

Découvrez notre glossaire JavaScript

Tous les mots de JavaScript expliqués simplement : mots-clés, objets natifs, méthodes, erreurs et concepts. Définitions claires et exemples qui tournent, pour apprendre et pour se dépanner.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.