Aller au contenu

Le média des artisans du web mercredi 16 septembre 2026

Front-end

Debouncing et throttling en JavaScript : réguler la cadence des événements

Saisie, défilement, redimensionnement : certains événements se déclenchent des dizaines de fois par seconde et saturent le fil d'exécution. Le debouncing et le throttling régulent cette cadence, chacun pour un besoin distinct qu'il faut savoir départager.

Debouncing et throttling en JavaScript

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èreDebouncingThrottling
Moment d’exécutionAprès une accalmieÀ intervalle régulier
Nombre d’appelsUn seul, à la finUn par fenêtre de temps
Valeur qui compteLa dernièreLes intermédiaires aussi
Cas typeAutocomplétion, validation de formulaireDé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.

Partager LinkedIn Bluesky Hacker News E-mail

À lire aussi