JavaScript setTimeout et setInterval
Apprenez à planifier du code en JavaScript avec setTimeout et setInterval : syntaxe, arguments, annulation, setTimeout récursif, délai zéro, debounce et throttle.
Parfois vous ne voulez pas exécuter du code immédiatement — vous souhaitez l'exécuter plus tard, ou l'exécuter de façon répétée. Les deux fonctions de planification de JavaScript, setTimeout() et setInterval(), vous permettent de faire exactement cela. Ce guide couvre leur syntaxe, comment passer des arguments, comment annuler un minuteur planifié, l'important modèle du « setTimeout récursif », le comportement surprenant d'un délai zéro, et deux utilisations concrètes : le debouncing et le throttling.
Aucune de ces fonctions ne fait partie du langage JavaScript de base — elles sont fournies par l'environnement hôte (navigateurs et Node.js). Le comportement décrit ici est identique dans les deux, avec quelques différences signalées au fil du texte.
Introduction aux fonctions de temporisation JavaScript
Un minuteur planifie l'exécution d'un callback une fois que le code en cours est terminé et qu'un certain laps de temps s'est écoulé. Parce que JavaScript est mono-thread, le callback n'interrompt jamais le code en exécution ; il attend dans une file d'attente et s'exécute uniquement lorsque la pile d'appels est vide. (Pour le mécanisme complet, voir La boucle d'événements.)
La fonction setTimeout()
setTimeout() exécute une fonction une seule fois après un délai spécifié. Elle accepte une fonction à exécuter et un délai en millisecondes avant cette exécution.
Syntaxe
let timerId = setTimeout(func, delay, arg1, arg2, ...);func— la fonction (ou, moins couramment, une chaîne de code) à exécuter.delay— le temps d'attente en millisecondes avant l'exécution. Par défaut à0.arg1, arg2, ...— arguments optionnels transmis directement àfunc.
La valeur de retour est un identifiant numérique de minuteur que vous pourrez passer ultérieurement à clearTimeout().
Exemple
Passer des arguments au callback
Tout ce que vous placez après le délai est transmis au callback. C'est plus propre que d'envelopper l'appel dans une autre fonction fléchée :
setTimeout(greet(), 1000) exécute greet() immédiatement et planifie sa valeur de retour (probablement undefined). Écrivez setTimeout(greet, 1000) — sans parenthèses.La fonction setInterval()
setInterval() a la même signature que setTimeout(), mais au lieu d'exécuter le callback une seule fois, elle l'exécute de façon répétée toutes les delay millisecondes jusqu'à ce que vous l'arrêtiez.
Syntaxe
let timerId = setInterval(func, delay, arg1, arg2, ...);Exemple
setTimeout récursif décrit ci-dessous.setTimeout récursif vs. setInterval
Vous pouvez reproduire setInterval() en faisant en sorte qu'un callback setTimeout() se replanifie lui-même. La différence clé : setInterval() mesure le délai entre les débuts, tandis que setTimeout() récursif le mesure entre la fin d'une exécution et le début de la suivante — garantissant une pause fixe même lorsque le callback est lent.
Annuler une exécution planifiée
Les deux fonctions retournent un identifiant de minuteur. Conservez cet identifiant et vous pourrez annuler le travail planifié avec clearTimeout() ou clearInterval(). (Les deux fonctions d'annulation sont en réalité interchangeables dans la plupart des moteurs, mais les faire correspondre au planificateur qui a créé l'identifiant rend le code plus lisible.)
Arrêter setTimeout()
Pour annuler un timeout, stockez l'identifiant retourné par setTimeout() et passez-le à clearTimeout() avant que le délai ne s'écoule.
Exemple
Arrêter setInterval()
De même, sauvegardez l'identifiant retourné par setInterval() et passez-le à clearInterval(). Sans cela, l'intervalle s'exécute indéfiniment (ou jusqu'à la fermeture de la page), ce qui est une source courante de fuites mémoire et de minuteurs incontrôlés.
Exemple
Le setTimeout à délai zéro
setTimeout(func, 0) n'exécute pas func immédiatement. Il planifie func pour qu'elle s'exécute dès que le code synchrone en cours s'est terminé. C'est un moyen pratique de « céder le contrôle » — pour laisser le navigateur repeindre l'interface ou pour découper une tâche longue en morceaux — et cela explique un ordre d'exécution qui surprend souvent les débutants :
Notez que les minuteurs sont des macrotâches : ils s'exécutent après toutes les microtâches en attente (comme les callbacks de promesses résolues). Voir Boucle d'événements : microtâches et macrotâches pour les règles d'ordonnancement précises.
Applications pratiques et conseils
Deux des utilisations les plus courantes de setTimeout() dans le monde réel sont le debouncing et le throttling — deux façons de limiter la fréquence d'exécution d'une fonction en réponse à des événements rapides.
Debouncing avec setTimeout()
Le debouncing attend qu'une série d'événements soit terminée avant d'exécuter la fonction. Chaque nouvel événement réinitialise le minuteur, de sorte que le callback ne se déclenche qu'après un silence de wait millisecondes. C'est idéal pour une zone de recherche : vous voulez envoyer une seule requête après que l'utilisateur a arrêté de taper, pas une par frappe.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Debounced Input Example</title>
<script>
// Debounce function to limit the rate at which a function is executed
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Function to be debounced
function fetchData(input) {
alert(`API call with input: ${input}`); // Placeholder for an API call
}
// Create a debounced version of fetchData
const debouncedFetchData = debounce(fetchData, 300);
// Add the debounced function to an event listener
function setup() {
document.getElementById('searchInput').addEventListener('input', (event) => {
debouncedFetchData(event.target.value);
});
}
// Ensure setup is called once the document is fully loaded
document.addEventListener('DOMContentLoaded', setup);
</script>
</head>
<body>
<h3>Type in the input field:</h3>
<input type="text" id="searchInput" placeholder="Start typing..." />
</body>
</html>Throttling avec setTimeout()
Le throttling exécute la fonction au plus une fois par limit millisecondes, quel que soit le nombre d'événements qui arrivent entre-temps. Là où le debouncing attend le silence, le throttling garantit une cadence régulière — parfait pour les gestionnaires scroll, resize ou mousemove qui se déclencheraient sinon des dizaines de fois par seconde. L'exemple ci-dessous utilise une approche à déclenchement initial (il s'exécute immédiatement au premier événement, puis impose l'intervalle) :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Throttled Scroll Event</title>
<style>
/* Simple styling for demonstration */
body, html {
height: 200%; /* Make the page scrollable */
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
#log {
position: fixed;
top: 0;
left: 0;
background: white;
border: 1px solid #ccc;
padding: 10px;
width: 300px;
}
</style>
</head>
<body>
<div id="log">Scroll to see the effect...</div>
<script>
// Throttle function using setTimeout
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function() {
const context = this;
const args = arguments;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, Math.max(0, limit - (Date.now() - lastRan)));
}
}
}
// Function to be throttled
function handleScroll() {
const log = document.getElementById('log');
log.textContent = `Scroll event triggered at: ${new Date().toLocaleTimeString()}`;
}
// Add event listener for scroll
window.addEventListener('scroll', throttle(handleScroll, 1000));
</script>
</body>
</html>Pièges à retenir
- Les délais sont un minimum, pas une garantie. Si la pile d'appels est occupée ou que la boucle d'événements est encombrée, le callback attend. Le nombre que vous passez est le plus tôt où il peut s'exécuter, pas une promesse.
- Les onglets en arrière-plan sont limités. La plupart des navigateurs ralentissent les minuteurs dans les onglets inactifs à environ une fois par seconde pour économiser de l'énergie, donc les animations et les sondages ralentissent lorsque l'onglet est masqué.
- Les timeouts imbriqués sont limités à ~4 ms. Après cinq appels
setTimeout()imbriqués, les navigateurs imposent un délai minimum d'environ 4 millisecondes, donc un délai0n'est jamais vraiment zéro dans les chaînes profondes. - Délai maximum. Un délai supérieur à
2147483647(2^31 − 1) déborde le champ 32 bits et est traité comme0, se déclenchant presque immédiatement au lieu d'être dans le futur lointain. - Liaison de
this. Lorsque vous passez une méthode commesetTimeout(obj.method, 1000), elle perd sonthis. Utilisez une fonction fléchée —setTimeout(() => obj.method(), 1000)— ouobj.method.bind(obj). - Nettoyez toujours. Annulez les intervalles (et les timeouts en attente) lorsqu'un composant est démonté ou que le travail n'est plus nécessaire, sinon vous fuirez des minuteurs et risquez d'opérer sur un état périmé.
Sujets connexes
- Introduction : les callbacks — la base sur laquelle reposent les minuteurs.
- La boucle d'événements : microtâches et macrotâches — pourquoi un timeout de
0ms s'exécute quand même en dernier. - Promise et Async/await — alternatives modernes pour séquencer le travail asynchrone.
- Récursion et pile — contexte pour le modèle
setTimeoutrécursif.
Conclusion
setTimeout() exécute du code une fois après un délai ; setInterval() l'exécute sur un calendrier répété ; clearTimeout() et clearInterval() les annulent. Souvenez-vous que les délais sont des minimums, passez les arguments après le délai plutôt qu'à l'intérieur d'une fonction enveloppante, optez pour le modèle setTimeout récursif lorsque vous avez besoin d'un espacement régulier, et nettoyez toujours les minuteurs dont vous n'avez plus besoin. Avec le debouncing et le throttling en plus, ces deux petites fonctions couvrent la plupart des travaux temporels que vous ferez dans le navigateur.