Smyčka událostí JavaScriptu

  • Úvod
  • Blokování smyčky událostí
  • Zásobník hovorů
  • Jednoduché vysvětlení smyčky událostí
  • Zařazení do fronty provádění funkce
  • Fronta zpráv
  • Fronta úloh ES6

Úvod

Smyčka událostí je jedním z nejdůležitějších aspektů JavaScriptu.

Roky jsem programoval pomocí JavaScriptu, ale nikdy se mi to nepodařilo úplně pochopil, jak to pod kapotou chodí. Je naprosto v pořádku, že tento koncept neznáte podrobně, ale jako obvykle je užitečné vědět, jak to funguje, a také byste v tomto bodě mohli být trochu zvědaví.

Tento příspěvek si klade za cíl vysvětlit vnitřní detaily toho, jak JavaScript funguje s jedním vláknem a jak zpracovává asynchronní funkce.

Váš kód JavaScript běží s jedním vláknem. V jednu chvíli se děje jen jedna věc.

Toto je omezení, které je ve skutečnosti velmi užitečné, protože hodně zjednodušuje, jak programujete, aniž byste se museli starat o problémy se souběžností.

Jen musíte věnovat pozornost tomu, jak píšete kód, a vyhnout se všemu, co by mohlo blokovat vlákno, jako jsou synchronní síťová volání nebo nekonečné smyčky.

Obecně platí, že ve většině prohlížečů existuje smyčka událostí pro každou kartu prohlížeče, aby byl každý proces izolovaný a aby se zabránilo tomu, že webová stránka s nekonečnými smyčkami nebo náročným zpracováním zablokuje celý váš prohlížeč.

Prostředí spravuje více smyček souběžných událostí, například pro zpracování volání API. Web Workers také běží ve vlastní smyčce událostí.

Musíte se hlavně starat o to, že váš kód poběží na jediné smyčce událostí a při psaní kódu je třeba mít na paměti tuto věc, aby nedošlo k jeho zablokování.

Blokování smyčky událostí

Jakýkoli kód JavaScript, kterému trvá příliš dlouho, než se vrátí zpět řízení do smyčky událostí, zablokuje provádění jakéhokoli kódu JavaScript na stránce, dokonce i zablokuje vlákno uživatelského rozhraní a uživatel nemůže klikat, posouvat stránku atd.

Téměř všechna I/O primitiva v JavaScriptu jsou neblokující. Síťové požadavky, operace souborového systému Node.js a tak dále. Blokování je výjimkou, a proto je JavaScript tolik založen na zpětných voláních a v poslední době na slibech a async/wait.

Zásobník hovorů

Zásobník volání je fronta LIFO (Last In, First Out).

Smyčka událostí nepřetržitě kontroluje zásobník hovorů abyste zjistili, zda je potřeba spustit nějakou funkci.

Přitom přidá jakékoli volání funkce, které najde, do zásobníku volání a provede každé z nich v pořadí.

Znáte trasování zásobníku chyb, které možná znáte, v ladicím programu nebo v konzole prohlížeče? Prohlížeč vyhledá názvy funkcí v zásobníku volání, aby vás informoval, která funkce je původcem aktuálního volání:

Jednoduché vysvětlení smyčky událostí

Vyberme si příklad:

Používám foo , bar a baz jako náhodná jména . Chcete-li je nahradit, zadejte jakýkoli druh názvu

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  bar()
  baz()
}

foo()

Tento kód se vytiskne

foo
bar
baz

podle očekávání.

Po spuštění tohoto kódu nejprve foo() je nazýván. Uvnitř foo() nejprve zavoláme bar() , pak zavoláme baz() .

V tomto okamžiku vypadá zásobník volání takto:

Smyčka událostí při každé iteraci se podívá, zda je něco v zásobníku volání, a provede to:

dokud není zásobník hovorů prázdný.

Spuštění funkce do fronty

Výše uvedený příklad vypadá normálně, není na něm nic zvláštního:JavaScript najde věci, které má provést, a spustí je v pořadí.

Podívejme se, jak odložit funkci, dokud není zásobník prázdný.

Případ použití setTimeout(() => {}), 0) je zavolat funkci, ale provést ji až po provedení každé jiné funkce v kódu.

Vezměte si tento příklad:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  baz()
}

foo()

Tento kód vytiskne, možná překvapivě:

foo
baz
bar

Když se tento kód spustí, zavolá se nejprve foo(). Uvnitř foo() nejprve zavoláme setTimeout a předáme bar jako argument a dáme mu pokyn, aby okamžitě běžel tak rychle, jak je to možné, a předal 0 jako časovač. Potom zavoláme baz().

V tomto okamžiku vypadá zásobník volání takto:

Zde je pořadí provádění všech funkcí v našem programu:

Proč se to děje?

Fronta zpráv

Když se zavolá setTimeout(), prohlížeč nebo Node.js spustí časovač. Jakmile časovač vyprší, v tomto případě ihned poté, co zadáme 0 jako časový limit, je funkce zpětného volání umístěna do Fronty zpráv .

Fronta zpráv je také místem, kde jsou události iniciované uživatelem, jako jsou události kliknutí nebo klávesnice nebo odpovědi načtení, řazeny do fronty dříve, než na ně má váš kód příležitost reagovat. Nebo také události DOM jako onLoad .

Smyčka upřednostňuje zásobník hovorů a nejprve zpracuje vše, co nalezne v zásobníku hovorů, a jakmile v něm nic není, přejde k vyzvednutí věcí ve frontě zpráv.

Nemusíme čekat na funkce jako setTimeout , načítání nebo jiné věci k provádění vlastní práce, protože je poskytuje prohlížeč a žijí ve vlastních vláknech. Pokud například nastavíte setTimeout časový limit na 2 sekundy, nemusíte čekat 2 sekundy – čekání probíhá jinde.

Fronta úloh ES6

ECMAScript 2015 představil koncept Job Queue, který používá Promises (také představený v ES6/ES2015). Je to způsob, jak provést výsledek asynchronní funkce co nejdříve, než aby byl umístěn na konec zásobníku volání.

Sliby, které se vyřeší před ukončením aktuální funkce, budou provedeny hned po aktuální funkci.

Připadá mi hezká analogie jízdy na horské dráze v zábavním parku:fronta zpráv vás zařadí na konec fronty, za všechny ostatní lidi, kde budete muset čekat, až na vás přijde řada, zatímco fronta na práci je lístek na rychlý průchod která vám umožní jet další jízdu hned poté, co dokončíte tu předchozí.

Příklad:

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {
  console.log('foo')
  setTimeout(bar, 0)
  new Promise((resolve, reject) =>
    resolve('should be right after baz, before bar')
  ).then(resolve => console.log(resolve))
  baz()
}

foo()

Toto se vytiskne

foo
baz
should be right after baz, before bar
bar

To je velký rozdíl mezi Promises (a Async/await, který je postaven na slibech) a obyčejnými starými asynchronními funkcemi prostřednictvím setTimeout() nebo jiná rozhraní API platformy.