Asynchroniczny JS pod maską
MDN definiuje asynchroniczny przepływ pracy jako
..rozpoczęcie potencjalnie długotrwałego zadania i nadal możliwość reagowania na inne zdarzenia podczas wykonywania tego zadania… Po zakończeniu tego zadania program jest prezentowany z wynikiem.
Ale jak dokładnie można to osiągnąć w Internecie za pomocą JavaScript?
Przed zagłębieniem się w implementacje asynchroniczne warto mieć podstawową wiedzę na temat JS na niższym poziomie.
Jak działa JS?
JavaScript jest jednowątkowy i interpretowany, co oznacza, że instrukcje są wykonywane bezpośrednio i nie mają etapu kompilacji ex-ante. Weźmy jako przykład najpopularniejszy silnik V8.
Na wysokim poziomie V8 analizuje kod, tworzy abstrakcyjne drzewo składni (AST) i kompiluje je do zoptymalizowanego kodu bajtowego za pomocą interpretera. Jest też preparser, dwa kompilatory i buforowanie, które sprawiają, że jest szybki (V8 ma świetne blogi na ten temat). Zapewnia nam tylko stos wywołań, stertę i pętlę zdarzeń ( tak jakby nie do końca) . Możemy zignorować stertę dla celów tej króliczej nory: jest to fragment nieustrukturyzowanej pamięci z odniesieniami do obiektów i zmiennymi.
Pętla zdarzeń w czasie wykonywania
W przeciwieństwie do Javy czy C++, JS pozwala na wykonanie jednego zdarzenia naraz. Możemy to zobaczyć w naszych przeglądarkach, śledząc ścieżkę połączenia za pomocą console.trace():
// shoutout to ES6 arrow functions
foo = () => {
console.log("Hello, World!");
bar();
}
bar = () => {
console.log("Goodbye, World!");
trace();
}
trace = () => console.trace();
foo();
Hello, World! VM1049:6
Goodbye, World! VM1049:11
// console.trace
trace @ VM1049:11
bar @ VM1049:7
foo @ VM1049:3
call stack visualization
Ustaw limit czasu
Uruchommy te same metody z asynchronicznym opóźnieniem 5000 ms z setTimeout().
foo = () => {
console.log("Hello, World!");
setTimeout(bar, 5000);
}
bar = () => {
console.log("Goodbye, World!");
trace();
}
trace = () => console.trace();
foo();
Hello, World! VM1526:2
// 3 days later...
Goodbye, World! VM1526:6
// console.trace
trace @ VM1382:10
bar @ VM1382:6
// setTimeout is invoked
b @ main-base.bundle.UM3…oFn_e5gQ.12.js:1723
setTimeout (async)
Cga.window.setTimeout @ main-base.bundle.UM3…toFn_e5gQ.12.js:516
// foo is pushed to the stack first
foo @ VM1382:3
setTimeoutnie jest częścią silnika JS, co ma sens, ponieważ silnik może obsłużyć jedno zdarzenie na raz. Przeglądarki są jednak wielowątkowe i zapewniają nam internetowe interfejsy API umożliwiające dostęp do implementacji setTimeout. Na przykład Chrome używa jednowątkowego silnika V8 w architekturze wieloprocesowej, w której każdy proces jest mocno wielowątkowy. W tym przypadku zostało to wyraźnie wywołane za pomocą Cga.setTimeout @ main-base.bundle.blah-blah-blah...Virtually, co umożliwia nam korzystanie z wielu wątków.
Pętla zdarzeń
Zmieńmy kolejność naszego bloku kodu:
foo = () => {
console.log("Hello, World!");
setTimeout(bar, 5000);
}
bar = () => {
setTimeout(() => console.log("Goodbye, World!"), 5000);
trace();
}
trace = () => console.trace();
foo();
// output of foo
Hello, World! VM1649:11
// console.trace
trace @ VM1694:11
bar @ VM1694:8
b @ main-base.bundle.UM3…oFn_e5gQ.12.js:1723
setTimeout (async)
Cga.window.setTimeout @ main-base.bundle.UM3…toFn_e5gQ.12.js:516
foo @ VM1694:3
// end trace
// 3 days later...
VM1649:6 Goodbye, World!
Środowisko JRE obejmuje:
- Silnik JS (jednowątkowy)
- Wirtualna maszyna Java (JVM) — Wirtualna maszyna, która wykonuje kod w czasie wykonywania. Przy okazji, właśnie dlatego „VM” dołącza dzienniki śledzenia.
- Internetowy interfejs API
- Kolejka zadań
- Pętla zdarzeń
Stos wywołań naszego silnika wyświetla zdarzenia jedno po drugim. Po wyświetleniu setTimeout() metoda wraz z wywołaniem zwrotnym (referencja do funkcji) jest delegowana do internetowego interfejsu API.
Zauważ, że silnik nie dba już o to, jak implementacja setTimeout interfejsu API obsługuje zdarzenie i wywołanie zwrotne. Silnik przechodzi dalej, aby wyświetlić następne zdarzenie i przekazać je Pacmanowi (maszynie JVM, która wykonuje kod).
W międzyczasie API wykorzystuje swoje wątki do odczekania 5000 ms, a następnie przekazuje je do kolejki zadań. Pętla zdarzeń widzi, że zdarzenie ustawia się w kolejce i wypycha je z powrotem na stos wywołań wtedy i tylko wtedy, gdy stos wywołań jest pusty.
Możesz użyć zabawnego wizualizatora Philipa Robertsa , aby zobaczyć to wszystko w akcji. Zobacz także jego niesamowite przemówienie na JSConf w 2014 roku.

![Czym w ogóle jest lista połączona? [Część 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































