Throttle decorator
Opret en “throttling” decorator throttle(f, ms) – der returnerer en wrapper.
Når den kaldes flere gange, sender den kaldet til f maksimalt én gang pr. ms millisekunder.
Sammenlignet med debounce decorator er adfæren helt anderledes:
debouncekører funktionen én gang efter “cooldown” perioden. Godt til at behandle det endelige resultat.throttlekører den ikke oftere end givetmstid. Godt til regelmæssige opdateringer der ikke bør ske for ofte.
Med andre ord er throttle en slags sekretær der accepterer telefonopkald uden at ville genere chefen (calls the actual f) mere end én ger per ms millisekunder.
Lad os se på et eksempel fra en realistisk situation for bedre at forstå metoden.
Tracking af musens bevægelser.
I en browser kan vi opsætte en funktion der modtager musens kooridnater og kaldes hver gang musen bevæger sig. I praksis vil sådan en funktion blive kaldt ret ofte – noget i stil med 100 gange i sekundet (hvert 10. millisekund).
Vi vil gerne opdatere informationen på siden når musen bevæger sig.
…Men funktionen update() der skal stå for opdateringen er alt for tung til at køre ved hver mikro-bevægelse. Der giver nok heller ingen mening at opdatere oftere end én gang pr. 100ms.
Derfor pakker vi den ind i en decorator: brug throttle(update, 100) som funktionen der skal køres ved hver mus-bevægelse i stedet for den originale update(). Decoratoren vil blive kaldt ofte, men videregive kaldet til update() maksimalt én gang pr. 100ms.
Det vil se ud i stil med dette:
- For den første bevægelse med musen vil den dekorerede variant med det samme videregive kaldet til
update. Det er vigtigt, brugeren ser vores reaktion på bevægelsen. - Derefter, mens musen bevæger sig videre og indtil
100mser gået, sker der intet. Den dekorerede variant ignorerer kaldene. - Ved slutningen af
100ms– en ekstraupdatesker med de sidste koordinater. - Til sidst, når musen stopper et sted, venter den dekorerede variant til
100mser udløbet og kører derefterupdatemed de sidste koordinater. Så er det vigtigt at de sidste koordinater bliver behandlet.
Et kodet eksempel:
function f(a) {
console.log(a);
}
// f1000 videregiver kald til f maks én gang per 1000 ms
let f1000 = throttle(f, 1000);
f1000(1); // vider 1
f1000(2); // (throttling, 1000ms ikke endnu)
f1000(3); // (throttling, 1000ms ikke endnu)
// når 1000 ms er gået...
// ...output'er 3, den midterste værdi 2 ignoreres
P.S. Argumenterne og konteksten this der gives til f1000 skal videregives til den originale f.
function throttle(func, ms) {
let isThrottled = false,
savedArgs,
savedThis;
function wrapper() {
if (isThrottled) { // (2)
savedArgs = arguments;
savedThis = this;
return;
}
isThrottled = true;
func.apply(this, arguments); // (1)
setTimeout(function() {
isThrottled = false; // (3)
if (savedArgs) {
wrapper.apply(savedThis, savedArgs);
savedArgs = savedThis = null;
}
}, ms);
}
return wrapper;
}
Et kald til throttle(func, ms) returnerer wrapper.
- Ved første kald kører
wrapperfunktionenfuncog sætter cooldown-tilstanden (isThrottled = true). - I denne tilstand gemmes alle kald i
savedArgs/savedThis. Bemærk at både konteksten og argumenterne er lige så vigtige og skal gemmes. Vi har brug for begge dele samtidigt for at kunne genskabe kaldet. - Efter
msmillisekunder har gået, udløsersetTimeout. Cooldown-tilstanden fjernes (isThrottled = false) og, hvis der var ignorerede kald, køreswrappermed de sidste gemte argumenter og kontekst.
Det tredje trin kører ikke func, men wrapper, fordi vi ikke kun skal køre func, men også igen indtaste cooldown-tilstanden og opsætte timeout’en til at nulstille den.