L'ordonnancement avec setTimeout et setInterval
Peut-être que nous ne voulons pas exécuter une fonction tout de suite, mais à un certain moment dans le futur. Cela s'appelle "ordonnancer (ou planifier) un appel de fonction".
Il existe deux méthodes pour cela :
setTimeoutpermet d'exécuter une fonction une unique fois après un certain laps de temps.setIntervalnous permet d'exécuter une fonction de manière répétée, en commençant après l'intervalle de temps, puis en répétant continuellement à cet intervalle.
Ces méthodes ne font pas partie de la spécification JavaScript. Mais la plupart des environnements ont un planificateur interne et fournissent ces méthodes. En particulier, elles sont supportées par tous les navigateurs et Node.js.
setTimeout
La syntaxe :
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
Les paramètres :
func|code : Fonction ou chaîne de caractères représentant du code à exécuter. En général, c'est une fonction. Pour des raisons historiques, une chaîne de caractères représentant du code peut être donnée en argument, mais ce n'est pas recommandé.
delay : La durée d'attente avant l'exécution, en millisecondes (1000ms = 1 seconde), par défaut 0.
arg1, arg2... : Arguments pour la fonction
Par exemple, le code ci-dessous appelle la fonction sayHi() une unique fois au bout de 1 seconde :
function sayHi() {
alert('Hello');
}
setTimeout(sayHi, 1000);
Dans le cas où fonction sayHi() requiert des arguments :
function sayHi(phrase, who) {
alert( phrase + ', ' + who );
}
setTimeout(sayHi, 1000, "Bonjour", "Jean"); // Bonjour, Jean
Si le premier argument est une chaîne de caractères, JavaScript crée alors une fonction à partir de celle-ci.
Ce qui fait que le code ci-dessous fonctionne aussi :
setTimeout("alert('Bonjour')", 1000);
Cependant, utiliser des chaînes de caractères n'est pas recommandé, il est préférable d'utiliser des fonctions fléchées à la place, comme ceci :
setTimeout(() => alert('Bonjour'), 1000);
Annuler une tâche avec clearTimeout
Un appel à setTimeout renvoie un "identifiant de timer" timerId que l'on peut utiliser pour annuler l'exécution de la fonction.
La syntaxe pour annuler une tâche planifiée est la suivante :
let timerId = setTimeout(...);
clearTimeout(timerId);
Dans le code ci-dessous, nous planifions l'appel à la fonction avant de l'annuler, au final rien ne s'est passé :
let timerId = setTimeout(() => alert("Je n'arriverai jamais"), 1000);
alert(timerId); // Identifiant du timer
clearTimeout(timerId);
alert(timerId); // Le même identifiant (ne devient pas null après l'annulation)
Comme on peut le voir dans les résultats des alert, dans notre navigateur, l'identifiant du timer est un nombre. Selon l'environnement, il peut être d'un autre type. Par exemple, Node.js renvoie un objet timer équipé d'autres méthodes.
Encore une fois, il n'y a pas de spécification universelle pour ces méthodes, donc ce n'est pas gênant.
Pour les navigateurs, les timers sont décrits dans la section des timers de HTML Living Standard.
setInterval
La méthode setInterval a la même syntaxe que setTimeout:
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
Tous ses arguments ont la même signfication que précédemment, mais contrairement à setTimeout, setInterval appelle la fonction non pas une fois, mais périodiquement après un interval de temps donné.
Afin d'annuler les appels futurs à la fonction, il est nécessaire d'appeler clearInterval(timerId).
L'exemple suivant affiche le message toutes les 2 secondes, puis arrête la tâche au bout de 5 secondes :
// Se répète toutes les 2 secondes
let timerId = setInterval(() => alert('tick'), 2000);
// S'arrête après 5 secondes
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
alert est affichéDans la majorité des navigateurs, dont Chrome et Firefox, le timer interne continue à s'incrémenter pendant qu'un message est affiché (via `alert`, `confirm` ou `prompt`).
Donc, si vous exécutez le code ci-dessus et que vous ne fermez pas la fenêtre `alert` pendant un certain temps, la prochaine `alert` sera affichée immédiatement lorsque vous le faites. L'intervalle réel entre les alertes sera inférieur à 2 secondes.
setTimeout imbriqué
Il y a deux façon d'ordonnancer l'exécution périodique d'une tâche.
L'un est setInterval. L'autre est un setTimeout imbriqué, comme ceci :
/** Au lieu de :
let timerId = setInterval(() => alert('tick'), 2000);
*/
let timerId = setTimeout(function tick() {
alert('tick');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);
Le setTimeout ci-dessus planifie le prochain appel de la fonction à la fin de l'appel en cours (*).
Le setTimeout imbriqué est une méthode plus flexible que setInterval. Ainsi, le prochain appel peut être programmé différemment, en fonction des résultats de l'appel en cours.
Par exemple, on peut avoir besoin d'implémenter un service qui envoie une requête à un serveur toutes les 5 secondes pour récupérer de la donnée, mais dans le cas où le serveur est surchargé, on doit augmenter le délai à 10 secondes, puis 20 secondes, 40 secondes...
Voici le pseudo-code correspondant :
let delay = 5000;
let timerId = setTimeout(function request() {
...send request...
if (request failed due to server overload) {
// Augmente l'intervalle avant le prochain appel
delay *= 2;
}
timerId = setTimeout(request, delay);
}, delay);
Ou par exemple, si les fonction qu'on souhaite planifier demandent beaucoup de ressources CPU, on peut alors mesurer leur temps d'exécution et planifier le prochain appel en fonction.
Et si les fonctions que nous planifions sont gourmandes en ressources processeur, nous pouvons mesurer le temps pris par l'exécution et planifier le prochain appel tôt ou tard.
Un setTimeout imbriqué permet de définir le délai entre les exécutions plus précisément que setInterval.
Comparons deux blocs de codes, le premier utilise setInterval :
let i = 1;
setInterval(function() {
func(i++);
}, 100);
Le second utilise un setTimeout imbriqué :
let i = 1;
setTimeout(function run() {
func(i++);
setTimeout(run, 100);
}, 100);
Dans le cas du setInterval l'ordonnanceur interne va appeler func(i++) toutes les 100ms :
Rien d'étrange ?
Le vrai délai entre deux appels à func est plus court que dans le code.
C'est normal car le temps d'exécution de func "consomme" une partie de ce délai.
Il est donc possible que le temps d'exécution de func soit plus long que prévu et prenne plus de 100ms.
Dans ce cas le moteur interne attend que l'exécution de func soit terminée, puis consulte l'ordonnanceur et si le délai est déjà "consommé", il réexécute la fonction *immédiatement*.
Dans ce cas extrême, si la fonction qui s'exécute met toujours plus de temps que delay ms, alors les appels successifs vont s'effectuer sans aucun temps de pause.
Et voici l'image pour le setTimeout imbriqué :
Le setTimeout imbriqué garantit le délai fixé (ici 100 ms).
Dans ce cas, c'est parce que le nouvel appel est planifié à la fin du précédent.
setTimeout sans délai
Il y a un cas d'usage particulier : setTimeout(func, 0) ou plus simplement setTimeout(func).
Ceci programme l'exécution de func dès que possible. Mais le planificateur ne l'invoquera qu'une fois le script en cours d'exécution terminé.
La fonction est donc programmée pour s'exécuter "juste après" le script en cours.
Par exemple, le code ci dessous affiche "Hello", et immédiatement après, "World" :
setTimeout(() => alert("World"));
alert("Hello");
La première ligne "met l'appel dans le calendrier après 0 ms". Mais le planificateur "vérifiera le calendrier" uniquement une fois le script en cours terminé. "Hello" est donc le premier, et "World" -- après.
Il y a aussi d'autres cas d'usage avancés d'ordonnancement à délai nul, spécifique au cas des navigateurs web, dont nous parlerons dans le chapitre <info:event-loop>.
Résumé
- Les méthodes
setInterval(func, delay, ...args)etsetTimeout(func, delay, ...args)permettent d'exécuterfuncrespectivement une seul fois/périodiquement aprèsdelaymillisecondes. - Pour annuler l'exécution, nous devons appeler
clearInterval/clearTimeoutavec la valeur renvoyée parsetInterval/setTimeout. - Les appels de
setTimeoutimbriqués sont une alternative plus flexible àsetInterval, ils permettent de configurer le temps *entre* les exécution plus précisément. - L'ordonnancement à délai nul avec
setTimeout(func, 0)(le même quesetTimeout(func)) permet de planifier l'exécution "dès que possible, mais seulement une fois que le bloc de code courant a été exécuté". - Le navigateur limite le délai minimal pour cinq appels imbriqués ou plus de
setTimeoutou poursetInterval(après le 5ème appel) à 4 ms. C'est pour des raisons historiques.
Veuillez noter que toutes les méthodes de planification ne garantissent pas le délai exact.
Par exemple, le timer interne au navigateur peut être ralenti pour de nombreuses raisons :
- Le CPU est surchargé.
- L'onglet du navigateur est en tâche de fond.
- L'ordinateur est en mode économie d'énergie.
Tout ceci peut augmenter la résolution de l'horloge (le délai minimum) jusqu'à 300ms voire 1000ms en fonction du navigateur et des paramètres de performance au niveau du système d'exploitation.