Nous pouvons décider d'exécuter une fonction non pas maintenant, mais à un certain moment plus tard. C'est ce qu'on appelle « planifier un appel ».
Il existe deux méthodes pour cela :
setTimeout
nous permet d'exécuter une fonction une fois après l'intervalle de temps.
setInterval
nous permet d'exécuter une fonction à plusieurs reprises, 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 disposent d’un planificateur interne et fournissent ces méthodes. Ils sont notamment pris en charge dans tous les navigateurs et Node.js.
La syntaxe :
laissez timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
Paramètres :
func|code
Fonction ou chaîne de code à exécuter. Habituellement, c'est une fonction. Pour des raisons historiques, une chaîne de code peut être transmise, mais ce n'est pas recommandé.
delay
Le délai avant l'exécution, en millisecondes (1 000 ms = 1 seconde), par défaut 0.
arg1
, arg2
…
Arguments pour la fonction
Par exemple, ce code appelle sayHi()
après une seconde :
fonction direSalut() { alert('Bonjour'); } setTimeout(sayHi, 1000);
Avec arguments :
fonction sayHi (phrase, qui) { alert( phrase + ', ' + qui ); } setTimeout(sayHi, 1000, "Bonjour", "John"); // Bonjour, Jean
Si le premier argument est une chaîne, JavaScript crée une fonction à partir de celle-ci.
Donc cela fonctionnera aussi :
setTimeout("alerte('Bonjour')", 1000);
Mais l'utilisation de chaînes n'est pas recommandée, utilisez plutôt des fonctions fléchées, comme ceci :
setTimeout(() => alert('Bonjour'), 1000);
Passez une fonction, mais ne l'exécutez pas
Les développeurs novices font parfois une erreur en ajoutant des parenthèses ()
après la fonction :
// faux! setTimeout(sayHi(), 1000);
Cela ne fonctionne pas, car setTimeout
attend une référence à une fonction. Et ici, sayHi()
exécute la fonction et le résultat de son exécution est transmis à setTimeout
. Dans notre cas, le résultat de sayHi()
n'est undefined
(la fonction ne renvoie rien), donc rien n'est planifié.
Un appel à setTimeout
renvoie un « timer identifier » timerId
que nous pouvons utiliser pour annuler l'exécution.
La syntaxe à annuler :
laissez timerId = setTimeout(...); clearTimeout(timerId);
Dans le code ci-dessous, nous planifions la fonction puis l'annulons (nous avons changé d'avis). Résultat, rien ne se passe :
let timerId = setTimeout(() => alert("n'arrive jamais"), 1000); alerte(timerId); // identifiant du minuteur clearTimeout(timerId); alerte(timerId); // même identifiant (ne devient pas nul après annulation)
Comme nous pouvons le voir à partir de la sortie alert
, dans un navigateur, l’identifiant du minuteur est un nombre. Dans d’autres environnements, cela peut être autre chose. Par exemple, Node.js renvoie un objet timer avec des méthodes supplémentaires.
Encore une fois, il n’existe pas de spécification universelle pour ces méthodes, donc ce n’est pas un problème.
Pour les navigateurs, les minuteries sont décrites dans la section minuteries de HTML Living Standard.
La méthode setInterval
a la même syntaxe que setTimeout
:
laissez timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
Tous les arguments ont la même signification. Mais contrairement à setTimeout
il exécute la fonction non seulement une fois, mais régulièrement après un intervalle de temps donné.
Pour arrêter d'autres appels, nous devrions appeler clearInterval(timerId)
.
L'exemple suivant affichera le message toutes les 2 secondes. Au bout de 5 secondes, la sortie est arrêtée :
// répète avec un intervalle de 2 secondes let timerId = setInterval(() => alert('tick'), 2000); // après 5 secondes, arrêt setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
Le temps passe pendant que alert
est affichée
Dans la plupart des navigateurs, y compris Chrome et Firefox, le minuteur interne continue de « tourner » tout en affichant alert/confirm/prompt
.
Ainsi, si vous exécutez le code ci-dessus et ne fermez pas la fenêtre alert
pendant un certain temps, la prochaine alert
s'affichera immédiatement au fur et à mesure que vous le faites. L'intervalle réel entre les alertes sera inférieur à 2 secondes.
Il existe deux manières d'exécuter quelque chose régulièrement.
L'un est setInterval
. L'autre est un setTimeout
imbriqué, comme ceci :
/** au lieu de: let timerId = setInterval(() => alert('tick'), 2000); */ laissez timerId = setTimeout (fonction tick() { alert('cocher'); timerId = setTimeout(tick, 2000); // (*) }, 2000);
Le setTimeout
ci-dessus planifie le prochain appel juste à la fin de celui en cours (*)
.
Le setTimeout
imbriqué est une méthode plus flexible que setInterval
. De cette façon, le prochain appel pourra être programmé différemment, en fonction des résultats de l'appel en cours.
Par exemple, nous devons écrire un service qui envoie une requête au serveur toutes les 5 secondes pour demander des données, mais en cas de surcharge du serveur, il devrait augmenter l'intervalle à 10, 20, 40 secondes…
Voici le pseudocode :
soit retard = 5000 ; let timerId = setTimeout (fonction request() { ...envoyer la demande... if (la demande a échoué en raison d'une surcharge du serveur) { // augmente l'intervalle jusqu'à la prochaine exécution retard *= 2 ; } timerId = setTimeout (demande, délai); }, retard);
Et si les fonctions que nous planifions sont gourmandes en CPU, nous pouvons alors mesurer le temps d'exécution et planifier le prochain appel tôt ou tard.
Nested setTimeout
permet de définir le délai entre les exécutions plus précisément que setInterval
.
Comparons deux fragments de code. Le premier utilise setInterval
:
soit je = 1 ; setInterval(fonction() { fonction(i++); }, 100);
Le second utilise setTimeout
imbriqué :
soit je = 1 ; setTimeout(fonction run() { fonction(i++); setTimeout(exécuter, 100); }, 100);
Pour setInterval
le planificateur interne exécutera func(i++)
toutes les 100 ms :
Avez-vous remarqué ?
Le délai réel entre les appels func
pour setInterval
est inférieur à celui du code !
C'est normal, car le temps d'exécution de func
« consomme » une partie de l'intervalle.
Il est possible que l'exécution de func
s'avère plus longue que prévu et prenne plus de 100 ms.
Dans ce cas, le moteur attend que func
soit terminée, puis vérifie le planificateur et si le temps est écoulé, le réexécute immédiatement .
Dans le cas extrême, si la fonction s'exécute toujours plus longtemps que delay
ms, alors les appels se produiront sans aucune pause.
Et voici l'image du setTimeout
imbriqué :
Le setTimeout
imbriqué garantit le délai fixe (ici 100 ms).
En effet, un nouvel appel est prévu à la fin du précédent.
Collecte des déchets et rappel setInterval/setTimeout
Lorsqu'une fonction est passée dans setInterval/setTimeout
, une référence interne y est créée et enregistrée dans le planificateur. Cela empêche la fonction d'être récupérée, même s'il n'y a aucune autre référence à celle-ci.
// la fonction reste en mémoire jusqu'à ce que le planificateur l'appelle setTimeout(function() {...}, 100);
Pour setInterval
la fonction reste en mémoire jusqu'à ce que clearInterval
soit appelé.
Il y a un effet secondaire. Une fonction fait référence à l'environnement lexical externe, donc, tant qu'elle existe, les variables externes existent également. Ils peuvent prendre beaucoup plus de mémoire que la fonction elle-même. Alors quand on n'a plus besoin de la fonction programmée, il vaut mieux l'annuler, même si elle est très petite.
Il existe un cas d'utilisation spécial : setTimeout(func, 0)
, ou simplement setTimeout(func)
.
Cela planifie 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 actuel.
Par exemple, ceci affiche « Bonjour », puis immédiatement « Monde » :
setTimeout(() => alert("Monde")); alert("Bonjour");
La première ligne « met l'appel dans le calendrier après 0 ms ». Mais le planificateur ne "vérifiera le calendrier" qu'une fois le script en cours terminé, donc "Hello"
est en premier et "World"
après.
Il existe également des cas d'utilisation avancés du délai d'attente zéro liés au navigateur, dont nous parlerons dans le chapitre Boucle d'événements : microtâches et macrotâches.
Un délai zéro n'est en fait pas nul (dans un navigateur)
Dans le navigateur, il existe une limitation quant à la fréquence d'exécution des minuteries imbriquées. Le HTML Living Standard dit : « après cinq minuteries imbriquées, l'intervalle est forcé à être d'au moins 4 millisecondes. ».
Montrons ce que cela signifie avec l'exemple ci-dessous. L'appel setTimeout
qu'il contient se replanifie sans délai. Chaque appel mémorise l'heure réelle du précédent dans le tableau times
. À quoi ressemblent les vrais retards ? Voyons:
let start = Date.now(); laissez fois = []; setTimeout(fonction run() { times.push(Date.now() - début); // mémorise le délai de l'appel précédent if (start + 100 < Date.now()) alert(times); // affiche les délais après 100 ms sinon setTimeout(run); // sinon reprogrammer }); // un exemple de résultat : // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
Les premiers timers s'exécutent immédiatement (comme indiqué dans la spécification), puis nous voyons 9, 15, 20, 24...
. Le délai obligatoire de 4+ ms entre les invocations entre en jeu.
La même chose se produit si nous utilisons setInterval
au lieu de setTimeout
: setInterval(f)
f
plusieurs fois avec un délai nul, puis avec un délai de plus de 4 ms.
Cette limitation vient des temps anciens et de nombreux scripts s’en inspirent ; elle existe donc pour des raisons historiques.
Pour JavaScript côté serveur, cette limitation n'existe pas et il existe d'autres moyens de planifier une tâche asynchrone immédiate, comme setImmediate pour Node.js. Cette note est donc spécifique au navigateur.
Les méthodes setTimeout(func, delay, ...args)
et setInterval(func, delay, ...args)
nous permettent d'exécuter la func
une fois/régulièrement après delay
quelques millisecondes.
Pour annuler l'exécution, nous devons appeler clearTimeout/clearInterval
avec la valeur renvoyée par setTimeout/setInterval
.
Les appels setTimeout
imbriqués sont une alternative plus flexible à setInterval
, nous permettant de définir plus précisément le temps entre les exécutions.
La planification sans délai avec setTimeout(func, 0)
(identique à setTimeout(func)
) est utilisée pour planifier l'appel « dès que possible, mais une fois le script en cours terminé ».
Le navigateur limite le délai minimal pour cinq appels imbriqués ou plus de setTimeout
ou pour setInterval
(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 minuteur du navigateur peut ralentir pour de nombreuses raisons :
Le processeur est surchargé.
L'onglet du navigateur est en mode arrière-plan.
L'ordinateur portable est en mode économie de batterie.
Tout cela peut augmenter la résolution minimale de la minuterie (le délai minimal) à 300 ms ou même 1 000 ms selon les paramètres de performances du navigateur et du système d'exploitation.
importance : 5
Écrivez une fonction printNumbers(from, to)
qui génère un nombre toutes les secondes, en commençant par from
et en terminant par to
.
Faites deux variantes de la solution.
Utilisation de setInterval
.
Utilisation de setTimeout
imbriqué.
Utilisation de setInterval
:
fonction printNumbers (de, à) { soit courant = de ; laissez timerId = setInterval(function() { alerte (actuel); si (actuel == à) { clearInterval(timerId); } actuel++ ; }, 1000); } // utilisation : printNombres (5, 10);
Utilisation de setTimeout
imbriqué :
fonction printNumbers (de, à) { soit courant = de ; setTimeout(fonction go() { alerte (actuel); si (actuel < à) { setTimeout(aller, 1000); } actuel++ ; }, 1000); } // utilisation : printNombres (5, 10);
Notez que dans les deux solutions, il existe un délai initial avant la première sortie. La fonction est appelée après 1000ms
la première fois.
Si nous voulons également que la fonction s'exécute immédiatement, nous pouvons alors ajouter un appel supplémentaire sur une ligne distincte, comme ceci :
fonction printNumbers (de, à) { soit courant = de ; fonction aller() { alerte (actuel); si (actuel == à) { clearInterval(timerId); } actuel++ ; } aller(); laissez timerId = setInterval(go, 1000); } printNombres (5, 10);
importance : 5
Dans le code ci-dessous, un appel setTimeout
est planifié, puis un calcul lourd est exécuté, qui prend plus de 100 ms pour se terminer.
Quand la fonction planifiée sera-t-elle exécutée ?
Après la boucle.
Avant la boucle.
Au début de la boucle.
Que va afficher alert
?
soit je = 0 ; setTimeout(() => alerte(i), 100); // ? // suppose que le temps d'exécution de cette fonction est >100 ms pour(soit j = 0; j < 100000000; j++) { je++; }
Tout setTimeout
ne s'exécutera qu'une fois le code actuel terminé.
Le i
sera le dernier : 100000000
.
soit je = 0 ; setTimeout(() => alerte(i), 100); // 100000000 // suppose que le temps d'exécution de cette fonction est >100 ms pour(soit j = 0; j < 100000000; j++) { je++; }