Une barre de recherche qui interroge le serveur à chaque touche, un gestionnaire de défilement qui recalcule une position à chaque pixel : ces motifs déclenchent des centaines d’appels inutiles et dégradent la réactivité perçue. Le debouncing et le throttling sont les deux réponses classiques. Elles se ressemblent, mais ne résolvent pas le même problème, et les confondre produit des interfaces soit trop lentes, soit trop nerveuses.
Un même symptôme, deux stratégies
Le point commun est le constat : un événement se répète bien plus vite que le traitement qu’on veut y attacher. La différence tient au moment choisi pour agir. Le debouncing attend une accalmie avant de déclencher le traitement une seule fois. Le throttling, lui, laisse passer un appel à intervalle régulier et ignore les autres. Le premier optimise pour « la dernière valeur compte », le second pour « un rythme maîtrisé suffit ».
Le debouncing : n’agir qu’une fois le calme revenu
Un debounce réarme un minuteur à chaque appel et n’exécute la fonction que si aucun nouvel appel n’est survenu pendant le délai. C’est le comportement attendu d’une autocomplétion : la requête ne part que lorsque l’utilisateur cesse de taper.
function debounce(fn, delay = 300) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const search = debounce((query) => {
fetch(`/api/search?q=${encodeURIComponent(query)}`);
}, 300);
input.addEventListener('input', (e) => search(e.target.value));Le réglage du délai est un compromis : trop court, la temporisation ne sert à rien ; trop long, l’interface paraît inerte. Entre 200 et 400 millisecondes couvre la majorité des champs de saisie. Ces utilitaires gagnent à être typés en TypeScript pour préserver la signature de la fonction enveloppée.
Le throttling : garantir un rythme régulier
Un throttle exécute la fonction immédiatement, puis bloque tout nouvel appel pendant une fenêtre donnée. Il convient aux flux continus où la valeur intermédiaire compte : défilement, déplacement de souris, redimensionnement.
function throttle(fn, interval = 200) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn.apply(this, args);
}
};
}
const onScroll = throttle(() => {
updateScrollProgress(window.scrollY);
}, 200);
window.addEventListener('scroll', onScroll, { passive: true });Pour les traitements purement visuels, requestAnimationFrame offre un throttling naturel calé sur le taux de rafraîchissement de l’écran, ce qui évite de recalculer plus souvent que le navigateur ne peint.
| Critère | Debouncing | Throttling |
|---|---|---|
| Moment d’exécution | Après une accalmie | À intervalle régulier |
| Nombre d’appels | Un seul, à la fin | Un par fenêtre de temps |
| Valeur qui compte | La dernière | Les intermédiaires aussi |
| Cas type | Autocomplétion, validation de formulaire | Défilement, redimensionnement, glisser |
Le debounce attend la fin ; le throttle impose un tempo. Choisir l’un pour l’autre, c’est traiter le mauvais problème.
Pièges courants et bonnes pratiques
La fonction régulée doit être créée une seule fois, pas à chaque rendu : dans un composant, une nouvelle instance à chaque cycle réinitialise le minuteur et annule tout l’effet. Il faut aussi retirer l’écouteur au démontage et, pour un debounce, prévoir une méthode d’annulation lorsque le contexte disparaît, par exemple à la fermeture d’un champ.
Repère de choix. Si seule la valeur finale importe, un debounce suffit. S’il faut un retour continu mais borné, un throttle. Et si le calcul ne sert qu’à l’affichage, requestAnimationFrame rend souvent les deux inutiles.
Enfin, mieux vaut connaître l’implémentation avant de dépendre d’une bibliothèque. Les versions de lodash gèrent des options fines, comme le déclenchement en début et en fin de fenêtre, mais une poignée de lignes maison couvre la plupart des besoins et évite une dépendance pour un comportement que l’on maîtrise. Cette économie de code participe de la même hygiène de cohérence côté interface que le reste du front-end.
Ce qu’il faut retenir
Debouncing et throttling ne s’opposent pas, ils se complètent. Le premier concentre une rafale d’événements en une action finale ; le second lisse un flux continu en un rythme régulier. Le bon réflexe consiste à nommer d’abord le besoin — dernière valeur ou cadence bornée — puis à choisir la technique, et non l’inverse. Pour l’affichage pur, le rafraîchissement animé reste l’outil le plus économe.
Sur mes projets, neuf régulations sur dix se règlent avec une dizaine de lignes maison, sans dépendance. Je ne sors la bibliothèque que quand j’ai besoin d’un déclenchement en tête et en queue de fenêtre à la fois — le fameux leading + trailing — et là, autant ne pas le réécrire. Le vrai piège, celui qui m’a coûté le plus de temps à déboguer, reste la fonction recréée à chaque rendu. — Simon Janvier
Pour aller plus loin : la référence MDN sur requestAnimationFrame.
