Un champ de recherche qui interroge l'API à chaque frappe, un gestionnaire scroll déclenché soixante fois par seconde : certains événements arrivent bien trop souvent. Debounce et throttle limitent les appels, mais pas de la même façon.
Debounce : attendre que ça se calme
Le debounce repousse l'appel tant que les événements continuent d'arriver. La fonction ne s'exécute qu'une fois, après un temps de calme :
function debounce(fn, delai = 300) {
let minuteur;
return function (...args) {
clearTimeout(minuteur);
minuteur = setTimeout(() => fn.apply(this, args), delai);
};
}
const champ = document.querySelector('#recherche');
champ.addEventListener(
'input',
debounce(async (event) => {
const q = encodeURIComponent(event.target.value);
const reponse = await fetch(`/api/recherche?q=${q}`);
afficherResultats(await reponse.json());
}, 250),
);
Throttle : au maximum une fois par intervalle
Le throttle, lui, garantit un rythme régulier : la fonction s'exécute au plus une fois toutes les n millisecondes, même si les événements continuent.
function throttle(fn, intervalle = 100) {
let dernierAppel = 0;
return function (...args) {
const maintenant = Date.now();
if (maintenant - dernierAppel >= intervalle) {
dernierAppel = maintenant;
fn.apply(this, args);
}
};
}
window.addEventListener(
'scroll',
throttle(() => {
const progression = window.scrollY / (document.body.scrollHeight - innerHeight);
barre.style.transform = `scaleX(${progression})`;
}, 50),
{ passive: true },
);
La version TypeScript
Avec un générique sur les arguments, la fonction renvoyée garde la signature de la fonction d'origine :
export function debounce<A extends unknown[]>(
fn: (...args: A) => void,
delai = 300,
): (...args: A) => void {
let minuteur: ReturnType<typeof setTimeout> | undefined;
return (...args: A) => {
clearTimeout(minuteur);
minuteur = setTimeout(() => fn(...args), delai);
};
}
const sauvegarder = debounce((id: number, contenu: string) => {
void api.enregistrer(id, contenu);
}, 1000);
sauvegarder(42, 'brouillon'); // ✔ typé
Lequel choisir ?
| Situation | Technique | Pourquoi |
|---|---|---|
| Recherche pendant la frappe | Debounce | Seule la saisie finale compte |
| Sauvegarde automatique | Debounce | On enregistre quand l'utilisateur fait une pause |
| Barre de progression au défilement | Throttle | Il faut des mises à jour régulières |
| Redimensionnement de fenêtre | Les deux | Throttle pour l'aperçu, debounce pour le calcul final |
Pour les animations liées au défilement,
requestAnimationFrameest souvent encore mieux qu'un throttle : il s'aligne sur le rafraîchissement de l'écran.