setTimeout: optimalizace dlouhých úloh v JS
Metoda s použitím funkce setTimeout()
slouží k optimalizaci dlouhých úloh v JavaScriptu, které blokují uživatelské interakce a zhoršují tím metriky jako Total Blocking Time (TBT) nebo Interaction to Next Paint (INP), která je důležitou součástí sady Core Web Vitals.
Problém? Dlouhé úlohy v JavaScriptu
JavaScript v prohlížeči pracuje na principu jednoho vlákna. V praxi to znamená, že všechny akce, události a interakce se řadí do systémů front, které ovládá mechanismus zvaný event loop.
Z pohledu optimalizace rychlosti webu nastává problém, když nějaká úloha trvá dlouho, a tím blokuje ostatní úlohy. Toto v důsledku blokuje celý prohlížeč a jeho hlavní vlákno tzv. main thread.
Dlouhé úlohy trvají déle než 50 ms a při měření v DevTools prohlížeče je poznáte podle červeného zvýraznění.
setTimeout() a optimalizace INP
Pro optimalizaci vysoké hodnoty metriky INP, je nutné rozdělit kód podle důležitosti, odložit méně důležité části a díky tomu nechat prohlížeči prostor, pro zpracování dalších uživatelských interakcí.
Před optimalizací uživatel po vstupu („Input received“) musí čekat na provedení dlouhé úlohy („Task“) na spuštění akce („Event“). Po optimalizace je dlouhá úloha rozbitá na menší a tudíž je akce spuštěna dříve.
V JavaScriptu se s dlouhými úlohami dá vypořádat využíváním tzv. asynchronních operací. Mezi ně patří API časovačů v čele s funkcí window.setTimeout()
. Ta umožňuje odložit námi vybrané úlohy. Funkce setTimeout()
má navíc jednu unikátní vlastnost. Pokud se nastaví čas nulový zpoždění, je dost pravděpodobné, že prohlížeč danou úlohu zpracuje odloží do následujícího renderovacím cyklu:
setTimeout(() => {
kod_odlozeny_do_nasledujiciho_renderu();
}, 0);
Díky této vlastnosti lze poměrně rychle kód rozdělit a méně důležité úlohy odložit o jeden renderovací cyklus. Tím vznikne tolik potřebný prostor, kdy si prohlížeč převezme kontrolu a vykreslí výstup důležitého kódu na obrazovku:
function saveSettings() {
// Do critical work that is user-visible:
validateForm();
showSpinner();
updateUI();
// Defer work that isn't user-visible to a separate task:
setTimeout(() => {
saveToDatabase();
sendAnalytics();
}, 0);
}
V aktuálním renderovacím cyklu provedeme update v UI. Zapsání události do databáze a analytiky odložíme a provedeme asynchronně. Díky tomu rozdělíme úlohu na dvě.