Fermer une notification au bout de trois secondes, relancer une requête après un échec, laisser une animation se terminer avant de changer d'écran : autant de moments où il faut simplement attendre.
Le langage n'a pas d'instruction pour cela, et c'est heureux : figer le fil unique gèlerait toute la page. L'environnement fournit un minuteur à la place.
Définition
setTimeout(callback, delai) confie une fonction à l'environnement, qui la déposera dans la file des tâches une fois le délai écoulé, exprimé en millisecondes. La ligne suivante s'exécute immédiatement, sans attendre.
console.log("avant");
const id = setTimeout(() => console.log("après 500 ms"), 500);
console.log("après, tout de suite");
// avant
// après, tout de suite
// après 500 msL'appel renvoie un identifiant, à conserver pour pouvoir annuler : clearTimeout(id) retire le callback de la file s'il n'est pas encore parti. Annuler un minuteur déjà déclenché ne provoque aucune erreur.
Le délai est un plancher, pas une garantie
Le nombre passé indique le moment à partir duquel le callback devient éligible. Il s'exécutera à ce moment-là seulement si la pile d'appels est libre, ce qui explique tous les retards observés.
- Un traitement en cours retarde tout. Une fonction longue occupe le fil, et le callback attend son tour derrière elle.
- Les délais s'imbriquent mal. Dans un navigateur, à partir de cinq minuteurs imbriqués, le délai minimal est ramené à quatre millisecondes par la spécification.
- Un onglet en arrière-plan est ralenti. Les navigateurs espacent fortement les minuteurs d'un onglet caché pour économiser la batterie.
const t0 = Date.now();
setTimeout(() => console.log("prévu à 50 ms, réel :", Date.now() - t0), 50);
const fin = Date.now() + 200;
while (Date.now() < fin) {} // le fil est occupé 200 msUne version qui rend une promesse
Un setTimeout emballé dans une Promise devient une pause utilisable avec await, et le code retrouve sa lecture verticale.
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
async function reessayer(travail, essais = 3) {
for (let i = 0; i < essais; i++) {
try {
return await travail();
} catch (err) {
if (i === essais - 1) throw err;
await pause(200 * (i + 1)); // attente croissante entre deux essais
}
}
}Les arguments placés après le délai sont transmis au callback : setTimeout(afficher, 100, "Chloé", 3) appellera afficher("Chloé", 3). Cela évite une fonction fléchée intermédiaire quand les valeurs sont déjà connues.
Questions fréquentes
Que vaut vraiment setTimeout(fn, 0) ?
Il ne signifie pas tout de suite, mais dès que la pile sera libre. C'est la façon habituelle de repousser un traitement après la fin du code en cours, par exemple pour laisser le navigateur peindre une mise à jour avant de démarrer un calcul.
Que contient l'identifiant renvoyé ?
Cela dépend de l'environnement : un simple nombre dans un navigateur, un objet Timeout sous Node.js. Traitez-le comme une valeur opaque, à passer telle quelle à clearTimeout, et ne comptez jamais sur son type dans un code partagé entre les deux.
Pourquoi mon minuteur utilise-t-il une ancienne valeur ?
Parce que le callback lit la variable au moment où il s'exécute, pas au moment où il est planifié. Dans une boucle, un var partagé donnera la dernière valeur à tous les minuteurs, là où un let crée une liaison par tour et donne le résultat attendu.