Dans le développement d'interfaces modernes, nous sommes souvent confrontés à des événements qui se déclenchent à une fréquence extrêmement élevée, comme scroll, mousemove ou resize. Sans contrôle, l'exécution répétée de fonctions complexes lors de ces événements peut paralyser les performances du navigateur. C'est ici qu'intervient le Throttling (ou "étranglement").
Comprendre le concept de Throttling
Le throttling est une technique d'optimisation qui garantit qu'une fonction n'est exécutée qu'une seule fois sur un intervalle de temps donné, peu importe le nombre de fois où l'événement déclencheur se produit. Contrairement au Debouncing, qui attend une période d'inactivité avant d'agir, le Throttling assure une exécution régulière et prévisible durant une action continue.
Imaginons un robinet : même si vous l'ouvrez à fond, un limiteur de débit peut forcer l'eau à ne couler que par gouttes régulières. En programmation, c'est exactement ce que nous faisons avec nos fonctions de rappel (callbacks).
1. Implémentation via l'horodatage (Timetsamp)
La méthode la plus simple consiste à enregistrer l'instant de la dernière exécution et à comparer cet instant avec le moment actuel. Si la différence est supérieure au délai souhaité, nous autorisons l'exécution.
function throttle(callback, delay) {
let lastExecution = 0;
return function(...args) {
const now = Date.now();
// Si le temps écoulé depuis la dernière fois est suffisant
if (now - lastExecution >= delay) {
lastExecution = now;
callback.apply(this, args);
}
};
}
// Exemple d'usage sur un scroll
const handleScroll = (event) => {
console.log("Position du scroll :", event.target.scrollTop);
};
window.addEventListener('scroll', throttle(handleScroll, 500));
Cette approche est efficace car elle déclenche la fonction immédiatement lors du premier appel. Cependant, elle présente un défaut : elle ignore souvent le dernier événement si celui-ci se produit à l'intérieur du dernier intervalle de temps.
2. Implémentation via un temporisateur (setTimeout)
Une autre stratégie utilise setTimeout. On vérifie si un minuteur est déjà en cours. Si ce n'est pas le cas, on en programme un qui exécutera la fonction et réinitialisera l'état après le délai.
function throttleWithTimer(func, wait) {
let timeoutId = null;
return function(...args) {
const context = this;
if (!timeoutId) {
timeoutId = setTimeout(() => {
func.apply(context, args);
timeoutId = null; // Libère le verrou après exécution
}, wait);
}
};
}
Contrairement à la méthode par horodatage, celle-ci garantit l'exécution finale, mais elle introduit un délai systématique au démarrage (la première exécution n'est pas immédiate).
3. Une solution hybride et optimisée
Pour obtenir le meilleur des deux mondes — une exécution immédiate au début et une exécution finale garantie à la fin — nous devons calculer dynamiquement le "temps restant" (remaining time).
function robustThrottle(fn, interval) {
let timerId;
let lastTime = 0;
return function(...args) {
const context = this;
const currentTime = Date.now();
// Calcul du temps restant avant la prochaine exécution autorisée
const remaining = interval - (currentTime - lastTime);
// Si le délai est écoulé ou si l'horloge système a été modifiée
if (remaining <= 0 || remaining > interval) {
if (timerId) {
clearTimeout(timerId);
timerId = null;
}
lastTime = currentTime;
fn.apply(context, args);
} else if (!timerId) {
// On programme l'exécution pour capturer le dernier mouvement
timerId = setTimeout(() => {
lastTime = Date.now();
timerId = null;
fn.apply(context, args);
}, remaining);
}
};
}
Pourquoi cette version est-elle supérieure ?
Cette implémentation gère trois cas de figure essentiels :
- Réactivité initiale : Si vous n'avez pas appelé la fonction récemment, elle s'exécute instantanément (grâce à la condition
remaining <= 0). - Précision : Au lieu d'attendre systématiquement le délai complet
interval, lesetTimeoutn'atetnd que le strict nécessaire (remaining). - Intégrité des données : Elle garantit que la dernière position d'un scroll ou d'un redimensionnement de fenêtre sera bien traitée, même après l'arrêt du mouvement de l'utilisateur.
En tant qu'ingénieur, le choix entre ces méthodes dépend du cas d'usage. Pour un bouton de soumission de formulaire (throttle simple), l'horodatage suffit. Pour une barre de progression de lecture liée au défilement, la version hybride est indispensable pour assurer une interface fluide et précise.