Plánování:setTimeout a setInterval

Můžeme se rozhodnout provést funkci ne hned, ale v určitou dobu později. Říká se tomu „naplánování hovoru“.

Existují dva způsoby:

  • setTimeout nám umožňuje spustit funkci jednou po uplynutí časového intervalu.
  • setInterval nám umožňuje spouštět funkci opakovaně, počínaje po časovém intervalu a poté se v tomto intervalu nepřetržitě opakovat.

Tyto metody nejsou součástí specifikace JavaScriptu. Ale většina prostředí má interní plánovač a poskytuje tyto metody. Zejména jsou podporovány ve všech prohlížečích a Node.js.

setTimeout

Syntaxe:

let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)

Parametry:

func|code
Funkce nebo řetězec kódu k provedení. Obvykle je to funkce. Z historických důvodů lze předat řetězec kódu, ale to se nedoporučuje.
delay
Prodleva před spuštěním v milisekundách (1000 ms =1 sekunda), ve výchozím nastavení 0.
arg1 , arg2
Argumenty pro funkci (nepodporováno v IE9-)

Tento kód například volá sayHi() po jedné sekundě:

function sayHi() {
 alert('Hello');
}

setTimeout(sayHi, 1000);

S argumenty:

function sayHi(phrase, who) {
 alert( phrase + ', ' + who );
}

setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John

Pokud je prvním argumentem řetězec, JavaScript z něj vytvoří funkci.

Takže to bude také fungovat:

setTimeout("alert('Hello')", 1000);

Ale použití řetězců se nedoporučuje, použijte místo nich funkce se šipkami, jako je tato:

setTimeout(() => alert('Hello'), 1000);
Předat funkci, ale nespouštět ji

Začínající vývojáři někdy dělají chybu přidáním hranatých závorek () za funkcí:

// wrong!
setTimeout(sayHi(), 1000);

To nefunguje, protože setTimeout očekává odkaz na funkci. A zde sayHi() spustí funkci a výsledek jejího spuštění je předán setTimeout . V našem případě výsledek sayHi() je undefined (funkce nic nevrací), takže není nic naplánováno.

Zrušení pomocí clearTimeout

Volání na číslo setTimeout vrátí „identifikátor časovače“ timerId které můžeme použít ke zrušení exekuce.

Syntaxe ke zrušení:

let timerId = setTimeout(...);
clearTimeout(timerId);

V níže uvedeném kódu naplánujeme funkci a poté ji zrušíme (rozmysleli jsme si to). V důsledku toho se nic neděje:

let timerId = setTimeout(() => alert("never happens"), 1000);
alert(timerId); // timer identifier

clearTimeout(timerId);
alert(timerId); // same identifier (doesn't become null after canceling)

Jak můžeme vidět z alert výstup, v prohlížeči je identifikátor č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.

Pro tyto metody opět neexistuje žádná univerzální specifikace, takže je to v pořádku.

Pro prohlížeče jsou časovače popsány v sekci časovače standardu HTML5.

setInterval

setInterval metoda má stejnou syntaxi jako setTimeout :

let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)

Všechny argumenty mají stejný význam. Ale na rozdíl od setTimeout spouští funkci nejen jednou, ale pravidelně po daném časovém intervalu.

Chcete-li zastavit další volání, měli bychom zavolat clearInterval(timerId) .

Následující příklad zobrazí zprávu každé 2 sekundy. Po 5 sekundách se výstup zastaví:

// repeat with the interval of 2 seconds
let timerId = setInterval(() => alert('tick'), 2000);

// after 5 seconds stop
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
Čas běží, dokud alert je ukázáno

Ve většině prohlížečů, včetně Chrome a Firefox, interní časovač stále „tiká“ a zobrazuje alert/confirm/prompt .

Pokud tedy spustíte výše uvedený kód a nezrušíte alert okno na nějakou dobu, pak další alert se zobrazí okamžitě, když to uděláte. Skutečný interval mezi upozorněními bude kratší než 2 sekundy.

Vnořený setTimeout

Existují dva způsoby, jak něco pravidelně provozovat.

Jedna je setInterval . Druhý je vnořený setTimeout , takto:

/** instead of:
let timerId = setInterval(() => alert('tick'), 2000);
*/

let timerId = setTimeout(function tick() {
 alert('tick');
 timerId = setTimeout(tick, 2000); // (*)
}, 2000);

setTimeout výše naplánuje další hovor přímo na konci aktuálního (*) .

Vnořené setTimeout je flexibilnější metoda než setInterval . Tímto způsobem může být další hovor naplánován odlišně v závislosti na výsledcích toho aktuálního.

Potřebujeme například napsat službu, která každých 5 sekund odesílá požadavek na server s žádostí o data, ale v případě přetížení serveru by se interval měl prodloužit na 10, 20, 40 sekund…

Zde je pseudokód:

let delay = 5000;

let timerId = setTimeout(function request() {
 ...send request...

 if (request failed due to server overload) {
 // increase the interval to the next run
 delay *= 2;
 }

 timerId = setTimeout(request, delay);

}, delay);

A pokud jsou funkce, které plánujeme, náročné na CPU, pak můžeme měřit čas potřebný k provedení a naplánovat další volání dříve nebo později.

Vnořeno setTimeout umožňuje nastavit prodlevu mezi spuštěními přesněji než setInterval .

Porovnejme dva fragmenty kódu. První používá setInterval :

let i = 1;
setInterval(function() {
 func(i++);
}, 100);

Druhý používá vnořený setTimeout :

let i = 1;
setTimeout(function run() {
 func(i++);
 setTimeout(run, 100);
}, 100);

Pro setInterval interní plánovač poběží func(i++) každých 100 ms:

Všimli jste si?

Skutečné zpoždění mezi func vyžaduje setInterval je méně než v kódu!

To je normální, protože čas zabral func Provedení 's „spotřebuje“ část intervalu.

Je možné, že func Provedení 's se ukázalo být delší, než jsme očekávali, a trvá více než 100 ms.

V tomto případě motor čeká na func dokončit, poté zkontroluje plánovač a pokud čas vypršel, spustí jej znovu okamžitě .

V případě hrany, pokud se funkce vždy provádí déle než delay ms, pak hovory proběhnou bez pauzy.

A zde je obrázek pro vnořený setTimeout :

Vnořený setTimeout zaručuje pevné zpoždění (zde 100 ms).

Je to proto, že nový hovor je naplánován na konci předchozího.

Sběr odpadu a zpětné volání setInterval/setTimeout

Když je funkce předána v setInterval/setTimeout , vytvoří se na něj interní odkaz a uloží se do plánovače. Zabraňuje tomu, aby byla funkce shromažďována, i když na ni nejsou žádné jiné odkazy.

// the function stays in memory until the scheduler calls it
setTimeout(function() {...}, 100);

Pro setInterval funkce zůstane v paměti až do clearInterval se nazývá.

Existuje vedlejší účinek. Funkce odkazuje na vnější lexikální prostředí, takže zatímco žije, žijí i vnější proměnné. Mohou zabírat mnohem více paměti než samotná funkce. Takže když už naplánovanou funkci nepotřebujeme, je lepší ji zrušit, i když je velmi malá.

Nulové zpoždění setTimeout

Existuje speciální případ použití:setTimeout(func, 0) , nebo jen setTimeout(func) .

Tím se naplánuje spuštění func co nejdříve. Plánovač jej však vyvolá až po dokončení aktuálně prováděného skriptu.

Funkce je tedy naplánována ke spuštění „hned po“ aktuálním skriptu.

Například se zobrazí „Ahoj“ a poté okamžitě „Svět“:

setTimeout(() => alert("World"));

alert("Hello");

První řádek „umístí hovor do kalendáře po 0 ms“. Plánovač však „zkontroluje kalendář“ až po dokončení aktuálního skriptu, takže "Hello" je první a "World" – po něm.

Existují také pokročilé případy použití časového limitu s nulovým zpožděním související s prohlížečem, které probereme v kapitole Smyčka událostí:mikroúlohy a makroúlohy.

Nulové zpoždění ve skutečnosti není nulové (v prohlížeči)

V prohlížeči existuje omezení, jak často se mohou vnořené časovače spouštět. Standard HTML5 říká:„Po pěti vnořených časovačích je interval nucen být alespoň 4 milisekundy.“

Ukažme si, co to znamená, na příkladu níže. setTimeout hovor se v něm přeplánuje s nulovým zpožděním. Každý hovor si pamatuje skutečný čas z předchozího v times pole. Jak vypadají skutečná zpoždění? Podívejme se:

let start = Date.now();
let times = [];

setTimeout(function run() {
 times.push(Date.now() - start); // remember delay from the previous call

 if (start + 100 < Date.now()) alert(times); // show the delays after 100ms
 else setTimeout(run); // else re-schedule
});

// an example of the output:
// 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100

První časovače běží okamžitě (jak je napsáno ve specifikaci) a pak vidíme 9, 15, 20, 24... . Do hry vstupuje povinná prodleva 4+ ms mezi vyvoláním.

Podobná věc se stane, pokud použijeme setInterval místo setTimeout :setInterval(f) běží f několikrát s nulovým zpožděním a poté se zpožděním 4+ ms.

Toto omezení pochází z dávných dob a spoléhá na něj mnoho písem, takže existuje z historických důvodů.

Pro JavaScript na straně serveru toto omezení neexistuje a existují jiné způsoby, jak naplánovat okamžitou asynchronní úlohu, jako je setImmediate pro Node.js. Tato poznámka je tedy specifická pro prohlížeč.

Shrnutí

  • Metody setTimeout(func, delay, ...args) a setInterval(func, delay, ...args) nám umožní spustit func jednou/pravidelně po delay milisekundy.
  • Chceme-li zrušit provádění, měli bychom zavolat clearTimeout/clearInterval s hodnotou vrácenou setTimeout/setInterval .
  • Vnořeno setTimeout volání jsou flexibilnější alternativou k setInterval , což nám umožňuje nastavit čas mezi exekuce přesněji.
  • Plánování s nulovým zpožděním s setTimeout(func, 0) (stejně jako setTimeout(func) ) se používá k naplánování volání „co nejdříve, ale po dokončení aktuálního skriptu“.
  • Prohlížeč omezuje minimální zpoždění pro pět nebo více vnořených volání setTimeout nebo pro setInterval (po 5. volání) na 4 ms. Je to z historických důvodů.

Upozorňujeme, že všechny metody plánování nezaručují přesné zpoždění.

Například časovač v prohlížeči se může zpomalit z mnoha důvodů:

  • Procesor je přetížený.
  • Karta prohlížeče je v režimu na pozadí.
  • Notebook je v režimu úspory baterie.

To vše může zvýšit minimální rozlišení časovače (minimální zpoždění) na 300 ms nebo dokonce 1 000 ms v závislosti na prohlížeči a nastavení výkonu na úrovni operačního systému.