Můžeme se rozhodnout, že funkci nespustÃme právÄ teÄ, ale až za nÄjakou dobu. To se nazývá âÄasovánà volánÃâ (âscheduling a callâ).
K tomu existujà dvÄ metody:
setTimeoutnám umožÅuje spustit funkci jednou po uplynutà zadaného Äasového intervalu.setIntervalnám umožÅuje spouÅ¡tÄt funkci opakovanÄ, nejprve po uplynutà zadaného Äasového intervalu a pak bude neustále v tomto intervalu opakována.
Tyto metody nejsou souÄástà specifikace JavaScriptu, ale vÄtÅ¡ina prostÅedà obsahuje internà plánovaÄ a tyto metody poskytuje. KonkrétnÄ jsou podporovány ve vÅ¡ech prohlÞeÄÃch a v Node.js.
setTimeout
Syntaxe:
let idÄasovaÄe = setTimeout(funkce|kód, [prodleva], [arg1], [arg2], ...)
Parametry:
funkce|kód- Funkce nebo ÅetÄzec kódu, který se má spustit. Obvykle to bývá funkce. Z historických důvodů lze pÅedat i ÅetÄzec kódu, ale to se nedoporuÄuje.
prodleva- Prodleva pÅed spuÅ¡tÄnÃm v milisekundách (1000 ms = 1 sekunda), standardnÄ 0.
arg1,arg2â¦- Argumenty této funkce.
NapÅÃklad tento kód zavolá ÅekniAhoj() po uplynutà jedné sekundy:
function ÅekniAhoj() {
alert('Ahoj');
}
setTimeout(ÅekniAhoj, 1000);
S argumenty:
function ÅekniAhoj(vÄta, kdo) {
alert( vÄta + ', ' + kdo );
}
setTimeout(ÅekniAhoj, 1000, "Ahoj", "Jan"); // Ahoj, Jan
Je-li prvnà argument ÅetÄzec, pak z nÄj JavaScript vytvoÅà funkci.
Bude tedy fungovat i tohle:
setTimeout("alert('Ahoj')", 1000);
PoužÃvánà ÅetÄzců se vÅ¡ak nedoporuÄuje, mÃsto nich použÃvejte Å¡ipkové funkce, napÅÃklad takto:
setTimeout(() => alert('Ahoj'), 1000);
ZaÄÃnajÃcà vývojáÅi nÄkdy dÄlajà chybu v tom, že za funkci pÅidajà závorky ():
// Å¡patnÄ!
setTimeout(ÅekniAhoj(), 1000);
To nefunguje, protože setTimeout oÄekává odkaz na funkci. A zde ÅekniAhoj() zavolá funkci a teprve výsledek jejÃho volánà se pÅedá funkci setTimeout. V naÅ¡em pÅÃpadÄ výsledek ÅekniAhoj() je undefined (funkce nic nevracÃ), takže se nic nenaÄasuje.
Zrušenà pomocà clearTimeout
Volánà setTimeout vrátà âidentifikátor ÄasovaÄeâ idÄasovaÄe, který můžeme použÃt ke zruÅ¡enà naÄasovaného spuÅ¡tÄnÃ.
Syntaxe zruÅ¡enÃ:
let idÄasovaÄe = setTimeout(...);
clearTimeout(idÄasovaÄe);
V následujÃcÃm kódu naÄasujeme funkci a pak ji zruÅ¡Ãme (rozmysleli jsme si to). Výsledkem bude, že se nic nestane:
let idÄasovaÄe = setTimeout(() => alert("tohle se nikdy nestane"), 1000);
alert(idÄasovaÄe); // identifikátor ÄasovaÄe
clearTimeout(idÄasovaÄe);
alert(idÄasovaÄe); // stejný identifikátor (po zruÅ¡enà se nevynuloval)
Jak vidÃme z výstupu alert, v prohlÞeÄi je identifikátorem ÄasovaÄe ÄÃslo. V jiných prostÅedÃch to může být nÄco jiného, napÅÃklad Node.js vracà objekt ÄasovaÄe s dalÅ¡Ãmi metodami.
Opakujeme, že pro tyto metody neexistuje žádná univerzálnà specifikace, takže je to v poÅádku.
Pro prohlÞeÄe jsou ÄasovaÄe popsány v kapitole o ÄasovaÄÃch HTML Living Standardu.
setInterval
Metoda setInterval má stejnou syntaxi jako setTimeout:
let idÄasovaÄe = setInterval(funkce|kód, [prodleva], [arg1], [arg2], ...)
VÅ¡echny argumenty majà stejný význam. Na rozdÃl od setTimeout vÅ¡ak funkce nebude spuÅ¡tÄna jenom jednou, ale pravidelnÄ vždy po uplynutà zadaného Äasového intervalu.
Chceme-li zastavit dalšà volánÃ, mÄli bychom zavolat clearInterval(idÄasovaÄe).
NásledujÃcà pÅÃklad zobrazà zprávu každé 2 sekundy. Po 5 sekundách bude výstup zastaven:
// opakovat s intervalem 2 sekundy
let idÄasovaÄe = setInterval(() => alert('tik'), 2000);
// konec po 5 sekundách
setTimeout(() => { clearInterval(idÄasovaÄe); alert('stop'); }, 5000);
alert, Äas plyne dálVe vÄtÅ¡inÄ prohlÞeÄů vÄetnÄ Chrome a Firefoxu v dobÄ, kdy je zobrazeno alert/confirm/prompt, vnitÅnà ÄasovaÄ âtikáâ dál.
Když si tedy spustÃte výše uvedený kód a okno alert nÄjakou dobu nezruÅ¡Ãte, pak bude následujÃcà alert zobrazen hned, jakmile to udÄláte. SkuteÄný interval mezi zobrazenÃmi tedy bude kratšà než 2 sekundy.
VnoÅený setTimeout
Existujà dva způsoby, jak nÄco pravidelnÄ spouÅ¡tÄt.
Prvnà je setInterval. Druhý je vnoÅený setTimeout, napÅÃklad takto:
/** namÃsto:
let idÄasovaÄe = setInterval(() => alert('tik'), 2000);
*/
let idÄasovaÄe = setTimeout(function tik() {
alert('tik');
idÄasovaÄe = setTimeout(tik, 2000); // (*)
}, 2000);
Uvedený setTimeout naÄasuje dalšà volánà na konci aktuálnÃho na Åádku (*).
VnoÅený setTimeout je flexibilnÄjšà metoda než setInterval. TÃmto způsobem můžeme pÅÃÅ¡tà volánà naÄasovat za jinou dobu, podle výsledků aktuálnÃho volánÃ.
NapÅÃklad potÅebujeme napsat službu, která každých 5 sekund poÅ¡le na server žádost o data, ale v pÅÃpadÄ, že je server pÅetÞen, by tento interval mÄla zvýšit na 10, 20, 40 sekundâ¦
Zde je pseudokód:
let prodleva = 5000;
let idÄasovaÄe = setTimeout(function požadavek() {
...pošle požadavek...
if (požadavek neuspÄl kvůli pÅetÞenà serveru) {
// zvýšÃme interval dalÅ¡Ãho spuÅ¡tÄnÃ
prodleva *= 2;
}
idÄasovaÄe = setTimeout(požadavek, prodleva);
}, prodleva);
A jestliže funkce, které Äasujeme, zatÄžujà CPU, můžeme mÄÅit dobu, jakou trvá jejich spuÅ¡tÄnÃ, a naplánovat si dalšà volánà na dÅÃve nebo pozdÄji.
VnoÅený setTimeout nám umožÅuje nastavovat prodlevu mezi volánÃmi pÅesnÄji než setInterval.
Porovnejme si dva fragmenty kódu. Prvnà použÃvá setInterval:
let i = 1;
setInterval(function() {
funkce(i++);
}, 100);
Druhý použÃvá vnoÅený setTimeout:
let i = 1;
setTimeout(function spusť() {
funkce(i++);
setTimeout(spusť, 100);
}, 100);
Pro setInterval vnitÅnà plánovaÄ spustà funkce(i++) každých 100 ms:
Všimli jste si?
SkuteÄná prodleva mezi volánÃmi funkce pro setInterval je nižšÃ, než uvedená v kódu!
To je normálnÃ, protože doba, kterou zabere výkon funkce, âspotÅebujeâ Äást intervalu.
Může se stát, že výkon funkce bude trvat déle, než jsme oÄekávali, a vyžádá si vÃce než 100 ms.
V takovém pÅÃpadÄ motor poÄká, až se funkce dokonÄÃ, pak zkontroluje plánovaÄ, a pokud jeho Äas vyprÅ¡el, spustà funkci okamžitÄ znovu.
V krajnÃm pÅÃpadÄ, jestliže výkon funkce trvá vždy déle než prodleva ms, bude k volánÃm docházet úplnÄ bez prodlevy.
A zde je obrázek pro vnoÅený setTimeout:
VnoÅený setTimeout zaruÄuje pevnou prodlevu (zde 100 ms) pÅed koncem jednoho volánà a zaÄátkem následujÃcÃho.
Je to proto, že nové volánà je naplánováno na konci pÅedchozÃho.
Když je do setInterval/setTimeout pÅedána funkce, vytvoÅà se na ni internà odkaz a ten se uložà do plánovaÄe. To bránà sbÄraÄi odpadků, aby funkci odstranil, i když na ni nejsou žádné jiné odkazy.
// funkce zůstane v pamÄti, dokud ji plánovaÄ nezavolá
setTimeout(function() {...}, 100);
Pro setInterval funkce zůstane v pamÄti, dokud nenà zavolán clearInterval.
To má vedlejšà efekt. Funkce se odkazuje na vnÄjšà lexikálnà prostÅedÃ, takže dokud existuje, existujà i vnÄjšà promÄnné. Ty mohou zabrat mnohem vÃce pamÄti než samotná funkce. Když tedy naÄasovanou funkci už nepotÅebujeme, je lepšà ji zruÅ¡it, i kdyby byla velmi malá.
setTimeout s nulovou prodlevou
Existuje speciálnà způsob použitÃ: setTimeout(funkce, 0) nebo jen setTimeout(funkce).
To naÄasuje výkon funkce na co nejdÅÃve. PlánovaÄ ji vÅ¡ak spustà až poté, co bude dokonÄen aktuálnÄ bÄžÃcà skript.
Funkce je tedy naÄasována ke spuÅ¡tÄnà âhned poâ aktuálnÃm skriptu.
NapÅÃklad tohle vypÃÅ¡e âAhojâ a hned pak âsvÄteâ:
setTimeout(() => alert("svÄte"));
alert("Ahoj");
Prvnà Åádek âuložà volánà do kalendáÅe za 0 msâ. AvÅ¡ak plánovaÄ âzkontroluje kalendáÅâ až poté, co bude aktuálnà skript dokonÄen, takže "Ahoj" půjde jako prvnà a "svÄte" až po nÄm.
V prohlÞeÄÃch existujà i pokroÄilejšà způsoby použità naÄasovánà s nulovou prodlevou, které probereme v kapitole SmyÄka událostÃ: mikroúlohy a makroúlohy.
V prohlÞeÄi je omezenÃ, jak Äasto se mohou vnoÅené ÄasovaÄe spouÅ¡tÄt. HTML Living Standard ÅÃká: âpo pÄti vnoÅených ÄasovaÄÃch musà být interval alespoÅ 4 milisekundy.â
Na následujÃcÃm pÅÃkladu si pÅedvedeme, co to znamená. Volánà setTimeout v nÄm naÄasuje samo sebe s nulovou prodlevou. Každé volánà si pamatuje v poli Äasy skuteÄný Äas, který uplynul od pÅedchozÃho. Jak budou vypadat skuteÄné prodlevy? PodÃvejme se:
let zaÄátek = Date.now();
let Äasy = [];
setTimeout(function spusť() {
Äasy.push(Date.now() - zaÄátek); // zapamatujeme si prodlevu od pÅedchozÃho volánÃ
if (zaÄátek + 100 < Date.now()) alert(Äasy); // za 100 ms zobrazÃme prodlevy
else setTimeout(spusÅ¥); // jinak znovu naÄasujeme
});
// pÅÃklad výstupu:
// 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
Nejprve se ÄasovaÄe spustà okamžitÄ (tak, jak je uvedeno ve specifikaci) a pak vidÃme 9, 15, 20, 24.... Povinná prodleva aspoÅ 4 ms mezi volánÃmi vstupuje do hry.
Podobná vÄc se stane, jestliže použijeme setInterval namÃsto setTimeout: setInterval(f) spustà f nÄkolikrát s nulovou prodlevou a poté s prodlevou nejménÄ 4 ms.
Toto omezenà pocházà ze starých Äasů a mnoho skriptů se na nÄ spoléhá, takže existuje z historických důvodů.
Pro JavaScript na stranÄ serveru toto omezenà neexistuje a jsou tam jiné způsoby, jak naÄasovat okamžitou asynchronnà Äinnost, napÅÃklad setImmediate pro Node.js. Tato poznámka je tedy specifická pro prohlÞeÄe.
ShrnutÃ
- Metody
setTimeout(funkce, prodleva, ...argumenty)asetInterval(funkce, prodleva, ...argumenty)nám umožÅujà spustit funkcifunkcejednou/pravidelnÄ poprodlevamilisekundách. - Chceme-li spuÅ¡tÄnà zruÅ¡it, mÄli bychom volat
clearTimeout/clearIntervals hodnotou, kterou vrátila funkcesetTimeout/setInterval. - FlexibilnÄjšà alternativou k
setIntervaljsou vnoÅená volánÃsetTimeout, která nám umožÅujà nastavit Äas mezi volánÃmi pÅesnÄji. - Nulová prodleva v
setTimeout(funkce, 0)(totéž jakosetTimeout(funkce)) se použÃvá k naÄasovánà volánà âco nejdÅÃve, ale až po dokonÄenà aktuálnÃho skriptuâ. - ProhlÞeÄ omezuje minimálnà prodlevu pro pÄt nebo vÃce vnoÅených volánÃ
setTimeoutnebo prosetInterval(po 5. volánÃ) na 4 ms. Je tomu tak z historických důvodů.
ProsÃme vÅ¡imnÄte si, že žádná Äasovacà metoda nezaruÄuje pÅesnou prodlevu.
NapÅÃklad ÄasovaÄ v prohlÞeÄi může zpomalit z mnoha důvodů:
- CPU je pÅetÞen.
- Záložka prohlÞeÄe bÄžà na pozadÃ.
- Laptop je v režimu úspory baterie.
To vÅ¡echno může zvýšit minimálnà rozliÅ¡enà ÄasovaÄe (minimálnà prodlevu) na 300 ms nebo dokonce 1000 ms v závislosti na prohlÞeÄi a nastavenà výkonu na úrovni OS.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)