2
votes

Javascript: augmentation de pile sans fin de récursivité de settimeout?

Mon objectif est un diaporama d'images d'arrière-plan avec HTML / CSS / JS. De nombreuses solutions que j'ai trouvées font la promotion de quelque chose comme ceci:

my_recursion();

function my_recursion () {
 // cycle the Background image ...
 setTimeout(my_recursion, 3000);
}

Ai-je tort de supposer que c'est un mauvais style? Je m'attendrais à ce que, par exemple, cycle 1000 toutes les 999 autres instances de my_recursion sont toujours ouvertes / sur la pile? Cela ne crée-t-il pas une pile infinie qui consomme de plus en plus de mémoire?

Ou y a-t-il une sorte d'intelligence impliquée qui fait quelque chose comme "si une fonction s'appelle à la fin, le (n-1) La fonction est détruite, y compris toutes les variables qui lui ont été assignées "?


10 commentaires

Il n'y a qu'une seule entrée de my_recursion sur la pile. La première exécution se termine complètement avant le lancement de la seconde.


Probablement pas exactement une dupe, mais j'ai déjà écrit sur la pile d'appels, la récursivité et setTimeout (en tant que mécanisme pour interagir avec la file d'attente) avant


Mais pourquoi? Cela signifie-t-il qu'un var x = 1 juste après mon setTimeout ne sera jamais exécuté?


@Robert les appels à setTimeout () reviennent immédiatement. Le système garde la trace de la minuterie en attente et appelle la fonction de rappel le moment venu.


@Robert il serait exécuté mais setTimeout planifiera la prochaine exécution après la fin de celle en cours en la plaçant dans la file d'attente.


D'accord, je vois. setTimeout revient immédiatement. Je pensais qu'il attendrait 3 secondes, puis appellerait la fonction, puis reviendrait à la fonction d'origine. Mais que se passe-t-il si après 3 secondes, ma fonction d'origine est toujours en cours d'exécution parce qu'il y a des calculs compliqués après l'appel setTimeout ? J'ai posté la même question sous la réponse ci-dessous.


@Robert non, ce n'est pas possible. Je vous exhorte à jeter un coup d'œil à l'autre question que j'ai liée et potentiellement à examiner davantage la file d'attente des événements. Si votre fonction est toujours en cours d'exécution dans 3s, rien d'autre ne fonctionnera . Ce n'est qu'à la fin que tout autre code planifié s'exécutera - vous n'obtiendrez pas deux exécutions parallèles.


D'accord merci, cela a du sens. Cette réponse a également aidé. Tout est poussé dans la file d'attente et exécuté à l'heure planifiée SI tout ce qui a été planifié avant la fin. Il s'agit donc d'un délai d'exécution MINIMUM tel que mentionné dans la réponse liée. Donc ce n'est pas une mauvaise pratique :) Merci!


@Robert exactement - l'essentiel ici est de comprendre la file d'attente. Le délai que vous ajoutez pour setTimeout est en fait plus une suggestion et le délai réel que vous obtenez peut différer.


De plus, la fonction n'est pas appelée de manière récursive, mais plutôt de manière répétitive. Le résultat serait le même que d'appeler la fonction plusieurs fois de manière séquentielle (sans délai dans ce cas bien sûr). Mais dans votre cas, la séquence d'appel est sans fin, sauf si vous ajoutez une condition avant setTimeout , ou effacez le timeout quelque part (ce qui n'est actuellement pas possible, puisque le id du timeout n'est stocké nulle part).


3 Réponses :


5
votes

Cela n'entraînera pas une augmentation infinie de la pile, à cause du fonctionnement de setTimeout, et à mon avis, ce n'est pas un mauvais style.

setTimeout ne garantit pas que le code s'exécutera directement après le délai imparti. Au lieu de cela, après ce délai, il poussera le rappel sur une "file d'attente", qui sera traitée lorsque la pile sera vide. Donc, il ne fonctionnera que lorsque my_recursion sera de retour et que la pile sera vide.

Si une fonction s'appelle à la fin (...)

my_recursion ne s'appelle nulle part. Il se passe simplement comme argument à setTimeout . Après cela, il continuera simplement à s'exécuter, reviendra directement après et sera sorti de la pile.

Cette présentation explique la pile et la file d'attente des événements.


5 commentaires

Cela signifie-t-il que si j'avais du code juste après setTimeout qui prenait plus de 3 secondes à s'exécuter (par exemple 4 secondes), la fonction suivante commencerait à s'exécuter en parallèle pendant ma (n-1) ème instance est toujours en cours d'exécution? Ou cela signifie-t-il que l'instance suivante démarre après 4 secondes car elle n'a pas pu démarrer après 3 puisque la (n-1) ème fonction était toujours en cours d'exécution?


Je ne pense pas qu'il soit nécessaire que la fonction s'appelle elle-même immédiatement et explicitement pour qu'elle soit récursive. Tant qu'elle aboutit à une autre exécution de la même fonction, elle doit être récursive. Si seuls les appels directs et explicites étaient requis, alors l'optimisation des appels de fin (TCO) serait importante pour déterminer si une fonction est récursive ou non.


@Robert: Non, le code ne s'exécuterait pas en parallèle. setTimeout ne garantit pas que le code s'exécutera directement après le délai imparti. Au lieu de cela, après ce délai, il poussera le rappel sur une "file d'attente", qui sera traitée lorsque la pile sera vide. Il ne fonctionnera donc que lorsque my_recursion sera de retour et que la pile sera vide.


J'ai ajouté un lien vers une présentation à JSConf qui explique cela.


@vlaz: Vous avez raison, j'ai mis à jour ma réponse pour qu'elle soit plus précise.



0
votes

Le code est correct. Il détruit toutes les variables car lorsque vous l'appelez pour la première fois. Il setTimeout () pour la fonction suivante et au dernier retour. Votre fonction ne retourne pas la suivante.

my_recursion();

function my_recursion () {
 // cycle the Background image ...
 setTimeout(my_recursion, 3000); //Sets timeout for next function.
 //returns undefined here
} 


0 commentaires

2
votes

Dans votre question, votre fonction n'a aucun paramètre. Dans une implémentation réelle, j'espère que vous prévoyez de les utiliser.

<p>Wait 3 seconds...</p>
p {
  text-align: center;
  font-size: 3vw;
  font-weight: bold;
  color: white;
}
const cycleBackground = (elem, bgs = [], ms = 1e3, i = 0) =>
  ( elem.setAttribute ('style', bgs[i])
  , setTimeout
      ( cycleBackground      // function to schedule
      , ms                   // when to schedule, ms from now
      , elem                 // user-specified element to change
      , bgs                  // user-specified backgrounds
      , ms                   // user-specified delay
      , (i + 1) % bgs.length // next background index
      )
  )

const backgrounds =
  [ "background-color: red;"
  , "background-image: linear-gradient(45deg, cyan 0%, purple 75%);"
  , "background-color: green;"
  ]

// call site
cycleBackground
  ( document.body // element to target
  , backgrounds   // list of backgrounds
  , 3e3           // delay, 3 seconds
  )


0 commentaires