
Toute explication de cette erreur vous dit la même chose, vraie et inutile : une fonction s’est appelée elle-même trop de fois, donc ajoutez un cas de base.
Vous le saviez. Ce que vous voulez savoir, c’est quel appel a déclenché cela, et c’est la seule erreur en JavaScript dont la pile d’appels est structurellement incapable de vous le dire.
Tout ce qui est mesuré ci-dessous a été exécuté dans Chrome. Les nombres sont ceux de V8 ; la forme tient dans tout moteur qui capture une pile au moment où l’erreur est levée.
Ce que la pile contient réellement
Prenez une chaîne où une fonction en appelle une autre, qui en appelle une troisième, qui se met à récurser, et attrapez l’erreur :
function entryPoint() { return level1() }
function level1() { return level2() }
function level2() { return recurse(0) }
function recurse(n) { return recurse(n + 1) }
try { entryPoint() } catch (e) { console.log(e.stack) }
RangeError: Maximum call stack size exceeded, puis dix lignes, chacune d’entre elles recurse :
RangeError: Maximum call stack size exceeded
at recurse (<anonymous>:2:145)
at recurse (<anonymous>:2:156)
at recurse (<anonymous>:2:156)
... seven more identical lines
entryPoint n’y est pas. Ni level1, ni level2. La pile nomme dix fois la fonction que vous
connaissiez déjà, et rien d’autre.
Dix, parce que Error.stackTraceLimit vaut 10 par défaut.
Augmenter la limite n’aide pas, et c’est la partie qu’il faut connaître
Le geste évident est de demander plus de frames :
Error.stackTraceLimit = 200;
Faites-le et vous obtenez 200 lignes. Mesuré sur la chaîne ci-dessus, les 200 sont recurse, et
entryPoint est toujours absent.
La raison tient à l’endroit depuis lequel la pile est capturée. Elle est prise au moment où l’erreur est levée, c’est-à-dire au point le plus profond de la pile, et elle remonte depuis là. Votre point d’entrée est à l’autre extrémité, à des milliers de frames. Demander 200 frames vous en apporte 200 depuis la mauvaise extrémité.
C’est pourquoi “lire la pile de bas en haut”, qui est la bonne consigne pour
presque toutes les autres erreurs, échoue ici. Le bas de cette pile est recurse aussi.
Capturez-la tôt, et la fenêtre est étroite
L’information que vous voulez existe. Elle n’est simplement disponible que pendant que la pile est encore peu profonde.
Capturez donc une pile depuis l’intérieur de la fonction qui récurse, lors d’un passage précoce, et jetez l’erreur :
function recurse(n) {
if (n === 2) console.log(new Error("where did this start").stack);
return recurse(n + 1);
}
Sur la chaîne ci-dessus, cette pile se termine ainsi :
at level2 (<anonymous>:4:27)
at level1 (<anonymous>:4:27)
at entryPoint (<anonymous>:3:31)
Et voilà la réponse. Toute la chaîne, dans l’ordre, depuis l’appel qui l’a déclenchée.
La fenêtre est à peu près aussi large que votre limite de frames, et c’est là le piège. Capturer
à la profondeur 2 donne le point d’entrée. Capturer à la profondeur 20, avec la limite encore à sa
valeur par défaut de 10, ne le donne plus, car à ce stade les dix frames que la pile retiendra ont
tous dépassé le début. Les deux gestes agissent donc ensemble et aucun n’agit seul : augmentez
Error.stackTraceLimit, et capturez lors d’un passage précoce.
Si vous ne savez pas quelle fonction récurse, l’erreur vous dit au moins cela. C’est le nom répété dix fois.
La version qui arrive dans un ticket
Tout ce qui précède suppose que vous êtes au clavier pendant que cela se produit. Voyez ce qui vous parvient quand cela arrive à quelqu’un d’autre.
La page s’est figée, ou est devenue blanche, ou un bouton a cessé de répondre. Si cette personne a ouvert la console et l’a photographiée, vous avez dix lignes identiques portant un nom de fonction. Cette image ne contient aucune information sur le chemin de code qui a été emprunté, et c’est la seule chose dont vous avez besoin.
Cela fait du débordement de pile un cas à part parmi les erreurs de navigateur. Pour la plupart des pannes, la console est l’artefact qu’il faut demander, et pour celle-ci elle est presque sans valeur : dix copies d’un fait que vous auriez pu deviner. Ce qui réduit vraiment le champ, c’est la suite d’actions juste avant le gel, parce que le point d’entrée est quelque chose que la personne a fait.
C’est une inversion inconfortable. L’erreur qui produit le plus de sortie console est celle où la sortie console aide le moins, et où ce que la personne faisait compte le plus.
Session Replay
Extension Chrome gratuite. Un clic sur la page qui pose problème enregistre la capture d'écran, la console et le journal réseau, et vous fournit un lien à coller dans le ticket.
Ce qu’il faut en retenir
L’erreur est honnête et presque vide de contenu : quelque chose a récursé sans s’arrêter. Elle nomme la fonction du bas de la boucle et taît celle du haut, non par avarice, mais à cause de l’endroit depuis lequel une pile d’appels est prise.
Alors arrêtez de lire cette pile, et allez en chercher une autre, lors d’un passage précoce, avec la limite augmentée. Ou, quand il s’agit du navigateur de quelqu’un d’autre, arrêtez de demander la console et demandez sur quoi la personne a appuyé.