Kiedy JavaScript jest synchroniczny?


202

Byłem pod wrażeniem, że JavaScript zawsze był asynchroniczny. Dowiedziałem się jednak, że istnieją sytuacje, w których tak nie jest (tj. Manipulacje DOM). Czy jest gdzieś dobre odniesienie, kiedy będzie synchroniczne i kiedy będzie asynchroniczne? Czy jQuery w ogóle na to wpływa?


14
Zawsze z wyjątkiem ajax.
defau1t

Zaakceptowana odpowiedź jest zła i wprowadza w błąd, uprzejmie sprawdź.
Suraj Jain,

2
Przydatne było także oglądanie youtube.com/watch?v=8aGhZQkoFbQ w celu zrozumienia pętli zdarzeń oraz sposobu działania stosu, internetowych interfejsów API i kolejki zadań w odniesieniu do synchronizacji i asynchronizacji
mtpultz

1
@ defau1t Czy to nie jest źle, JavaScript jest zawsze synchroniczny, kiedy zakończy się wywołanie ajax, wywołanie zwrotne kończy się w kolejce, jak to jest wyjątek od synchronicznej natury skryptu Java.
Suraj Jain,

Odpowiedzi:


281

JavaScript jest zawsze synchroniczny i jednowątkowy. Jeśli wykonujesz blok kodu JavaScript na stronie, żadne inne JavaScript na tej stronie nie będzie aktualnie wykonywane.

JavaScript jest tylko asynchroniczny w tym sensie, że może wykonywać na przykład wywołania Ajax. Wywołanie Ajax przestanie być wykonywane, a inny kod będzie mógł zostać wykonany, dopóki połączenie nie powróci (pomyślnie lub w inny sposób), w którym to momencie wywołanie zwrotne będzie działać synchronicznie. W tym momencie nie będzie działał żaden inny kod. Nie zakłóci to żadnego innego uruchomionego kodu.

Zegary JavaScript działają z tym samym rodzajem wywołania zwrotnego.

Opisywanie JavaScript jako asynchronicznego może być mylące. Dokładniej jest powiedzieć, że JavaScript jest synchroniczny i jednowątkowy z różnymi mechanizmami zwrotnymi.

jQuery ma opcję wywołań Ajax, aby były synchroniczne (z async: falseopcją). Początkujący mogą mieć pokusę, aby używać tego niepoprawnie, ponieważ pozwala to na bardziej tradycyjny model programowania, do którego można być bardziej przyzwyczajonym. Problemem jest to, że ta opcja blokuje cały JavaScript na stronie, aż do końca, w tym wszystkie programy obsługi zdarzeń i timery.


31
przepraszam, nie do końca zrozumiałem to stwierdzenie „Kod przestanie być wykonywany, dopóki połączenie nie zwróci (pomyślnie lub przez pomyłkę)”. czy mógłbyś opracować. Jak to stwierdzenie może być prawdziwe, gdy mówisz także: „Nie zakłóci to żadnego innego działającego kodu”; Czy mówisz o kodzie zwrotnym tylko w pierwszym wyciągu? Proszę, oświeć mnie.
krishna

2
Nettuts ma samouczek, który całkiem dobrze wyjaśnia podstawy asynchronizacji tutaj: net.tutsplus.com/tutorials/javascript-ajax/…
RobW

26
@cletus Instrukcja „Kod przestanie być wykonywany do momentu powrotu połączenia” wymaga korekty, ponieważ wykonanie się nie kończy. Wykonanie kodu może być kontynuowane. W przeciwnym razie oznaczałoby to, że połączenie jest synchroniczne.
HS.

1
Nie zrozumiałem też tego oświadczenia.
holuj

12
Ta odpowiedź jest niezwykle myląca i myląca. Zamiast tego zobacz odpowiedź CMS lub Faraz Ahmad.
iono

214

JavaScript jest jednowątkowy i ma synchroniczny model wykonania. Jednowątkowy oznacza, że ​​jedno polecenie jest wykonywane jednocześnie. Synchroniczny oznacza jeden po drugim, tzn. Jeden wiersz kodu jest wykonywany jednocześnie w celu wyświetlenia kodu. Tak więc w JavaScript jedna rzecz dzieje się na raz.

Kontekst wykonania

Silnik JavaScript współdziała z innymi silnikami w przeglądarce. Na stosie wykonawczym JavaScript jest u dołu kontekst globalny, a następnie, gdy wywołujemy funkcje, silnik JavaScript tworzy nowe konteksty wykonania dla odpowiednich funkcji. Gdy wywoływana funkcja kończy działanie, kontekst wykonywania jest wyskakujący ze stosu, a następnie kontekst kontekstu wykonania jest wyskakujący itd. ...

Na przykład

function abc()
{
   console.log('abc');
}


function xyz()
{
   abc()
   console.log('xyz');
}
var one = 1;
xyz();

W powyższym kodzie zostanie utworzony globalny kontekst wykonania, w tym kontekście var onebędzie przechowywany, a jego wartość będzie wynosić 1 ... po wywołaniu wywołania xyz () zostanie utworzony nowy kontekst wykonania i jeśli zdefiniowalibyśmy jakąkolwiek zmienną w funkcji xyz zmienne te byłyby przechowywane w kontekście wykonania xyz (). W funkcji xyz wywołujemy abc (), a następnie tworzony jest kontekst wykonania abc () i umieszczany na stosie wykonania ... Teraz, kiedy abc () skończy, jego kontekst jest wyskakujący ze stosu, a następnie kontekst xyz () jest wyskakujący z stos, a następnie kontekst globalny zostanie wyświetlony ...

Teraz o asynchronicznych wywołaniach zwrotnych; asynchroniczny oznacza więcej niż jeden na raz.

Podobnie jak stos wykonawczy, istnieje kolejka zdarzeń . Gdy chcemy otrzymywać powiadomienia o zdarzeniu w silniku JavaScript, możemy odsłuchać to zdarzenie, a to zdarzenie jest umieszczane w kolejce. Na przykład zdarzenie żądania Ajax lub zdarzenie żądania HTTP.

Ilekroć stos wykonawczy jest pusty, jak pokazano w powyższym przykładzie kodu, silnik JavaScript okresowo sprawdza kolejkę zdarzeń i sprawdza, czy istnieje jakieś zdarzenie, o którym należy powiadomić. Na przykład w kolejce były dwa zdarzenia: żądanie ajax i żądanie HTTP. Sprawdza również, czy istnieje funkcja, która musi zostać uruchomiona na wyzwalaczu zdarzenia ... Tak więc silnik JavaScript jest powiadamiany o zdarzeniu i zna odpowiednią funkcję do wykonania na tym zdarzeniu ... Tak więc silnik JavaScript wywołuje metodę funkcja obsługi, w przykładowym przypadku np. AjaxHandler () zostanie wywołana i jak zawsze, gdy wywoływana jest funkcja, jej kontekst wykonania jest umieszczony w kontekście wykonania, a teraz kończy się wykonywanie funkcji, a żądanie ajax zdarzenia jest również usuwane z kolejki zdarzeń ... Po zakończeniu AjaxHandler () stos wykonawczy jest pusty, więc silnik ponownie patrzy na kolejkę zdarzeń i uruchamia funkcję obsługi zdarzeń dla żądania HTTP, które było następne w kolejce. Należy pamiętać, że kolejka zdarzeń jest przetwarzana tylko wtedy, gdy stos wykonania jest pusty.

Na przykład zobacz poniższy kod wyjaśniający obsługę stosu i obsługę kolejek zdarzeń przez silnik Javascript.

function waitfunction() {
    var a = 5000 + new Date().getTime();
    while (new Date() < a){}
    console.log('waitfunction() context will be popped after this line');
}

function clickHandler() {
    console.log('click event handler...');   
}

document.addEventListener('click', clickHandler);


waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');

I

<html>
    <head>

    </head>
    <body>

        <script src="program.js"></script>
    </body>
</html>

Teraz uruchom stronę internetową i kliknij stronę i zobacz dane wyjściowe na konsoli. Wyjście będzie

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...

Mechanizm JavaScript uruchamia kod synchronicznie, jak wyjaśniono w części dotyczącej kontekstu wykonania, przeglądarka asynchronicznie umieszcza elementy w kolejce zdarzeń. Tak więc funkcje, których wykonanie zajmuje bardzo dużo czasu, mogą zakłócać obsługę zdarzeń. Rzeczy, które dzieją się w przeglądarce, takie jak zdarzenia, są obsługiwane w ten sposób przez JavaScript, jeśli ma być uruchomiony detektor, silnik uruchomi go, gdy stos wykonawczy będzie pusty. Zdarzenia są przetwarzane w kolejności ich wystąpienia, więc część asynchroniczna dotyczy tego, co dzieje się poza silnikiem, tj. Co silnik powinien zrobić, gdy zdarzają się te zdarzenia zewnętrzne.

JavaScript jest więc zawsze synchroniczny.


16
Ta odpowiedź jest bardzo jasna, powinna uzyskać więcej głosów pozytywnych.
ranu

7
Z pewnością najlepsze wyjaśnienie asynchronicznego zachowania JavaScript, które przeczytałem.
Charles Jaimet

1
Ładne wyjaśnienie kontekstu wykonania i kolejki.
Divyanshu Maithani,

1
Oczywiście wymaga to przeczytania nieco o stosie kontekstu wykonania, a jedynie dodanie go pustego i kolejki zdarzeń sprawia, że ​​w końcu mam wrażenie, że rozumiem determinację skryptu Java. Co gorsza, czuję, że zajmuje to tylko stronę czytania, ale nie znajduję go prawie nigdzie. Dlaczego więc nikt tego nie powiedział? Albo nie wiedzą, czy co? Ale czuję, że gdyby samouczek js miał to, mogłoby mi to zaoszczędzić dużo czasu. >: |
marszałkowiec

2
Idealne wyjaśnienie!
Julsy,

100

JavaScript jest jednowątkowy i cały czas pracujesz nad normalnym synchronicznym wykonywaniem przepływu kodu.

Dobrym przykładem asynchronicznego zachowania, jakie może mieć JavaScript, są zdarzenia (interakcja użytkownika, wyniki żądania Ajax itp.) I liczniki czasu, w zasadzie działania, które mogą wystąpić w dowolnym momencie.

Poleciłbym rzucić okiem na następujący artykuł:

Ten artykuł pomoże Ci zrozumieć jednowątkowy charakter JavaScript i jak wewnętrznie działają liczniki oraz jak działa asynchroniczne wykonywanie JavaScript.

asynchronizacja


Przyjęte odpowiedzi wprowadzają w błąd, czy możemy coś zrobić w tej sprawie? /
Suraj Jain

8

Dla kogoś, kto naprawdę rozumie, jak działa JS, to pytanie może się wydawać złe, jednak większość ludzi, którzy używają JS, nie ma tak głębokiego wglądu (i niekoniecznie potrzebuje go), a dla nich jest to dość mylące, spróbuj odpowiedzieć z tej perspektywy.

JS jest synchroniczny w sposobie wykonywania kodu. każda linia biegnie tylko za linią przed jej zakończeniem i jeśli linia ta wywołuje funkcję po zakończeniu ect ...

Główny punkt zamieszania wynika z faktu, że Twoja przeglądarka jest w stanie powiedzieć JS, aby wyciął więcej kodu w dowolnym momencie (na przykład, jak można wyciąć więcej kodu JS na stronie z konsoli). Jako przykład, JS ma funkcje oddzwaniania, których celem jest umożliwienie JS BEHAVE asynchronicznie, aby dalsze części JS mogły działać, czekając na wykonanie funkcji JS ( GETwywołanie IE ) w celu odpowiedzi, JS będzie działać do przeglądarka ma odpowiedź w tym momencie pętla zdarzeń (przeglądarka) wykona kod JS, który wywołuje funkcję wywołania zwrotnego.

Ponieważ pętla zdarzeń (przeglądarka) może wprowadzić więcej JS do wykonania w dowolnym momencie, w tym sensie JS jest asynchroniczny (podstawowe rzeczy, które spowodują, że przeglądarka wprowadzi kod JS, to limity czasu, wywołania zwrotne i zdarzenia)

Mam nadzieję, że jest to wystarczająco jasne, aby komuś pomóc.


4

Definicja

Określenia „asynchroniczny” można używać w nieco innych znaczeniach, co powoduje pozornie sprzeczne odpowiedzi, podczas gdy w rzeczywistości tak nie jest. Wikipedia na temat asynchronii ma następującą definicję:

Asynchronia w programowaniu komputerowym odnosi się do występowania zdarzeń niezależnych od głównego programu i sposobów radzenia sobie z takimi zdarzeniami. Mogą to być zdarzenia „zewnętrzne”, takie jak nadejście sygnałów lub działania inicjowane przez program, które odbywają się równocześnie z wykonywaniem programu, bez blokowania programu przez oczekiwanie na wyniki.

kod inny niż JavaScript może ustawiać w kolejce takie zdarzenia „zewnętrzne” do niektórych kolejek zdarzeń JavaScript. Ale to jest tak daleko, jak to możliwe.

Bez uprzedzenia

Nie ma zewnętrznej przerwy w uruchamianiu kodu JavaScript w celu wykonania innego kodu JavaScript w skrypcie. Fragmenty JavaScript są wykonywane jeden po drugim, a kolejność jest ustalana na podstawie kolejności zdarzeń w każdej kolejce zdarzeń oraz priorytetu tych kolejek.

Na przykład możesz być absolutnie pewien, że żaden inny kod JavaScript (w tym samym skrypcie) nigdy się nie uruchomi podczas wykonywania następującego fragmentu kodu:

let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
    sum += a[i];
}

Innymi słowy, JavaScript nie ma żadnego pierwszeństwa . Cokolwiek może być w kolejkach zdarzeń, przetwarzanie tych zdarzeń będzie musiało poczekać, aż taki fragment kodu dobiegnie końca. Specyfikacja EcmaScript mówi w sekcji 8.4 Zadania i kolejki zadań :

Wykonanie zadania można zainicjować tylko wtedy, gdy nie ma uruchomionego kontekstu wykonania, a stos kontekstu wykonania jest pusty.

Przykłady asynchronii

Jak już napisali inni, istnieje kilka sytuacji, w których asynchronia wchodzi w grę w JavaScript i zawsze wiąże się z kolejką zdarzeń, co może spowodować wykonanie JavaScript, gdy nie jest wykonywany żaden inny kod JavaScript:

  • setTimeout(): agent (np. przeglądarka) umieści zdarzenie w kolejce zdarzeń po upływie limitu czasu. Monitorowanie czasu i umieszczanie zdarzenia w kolejce odbywa się za pomocą kodu innego niż JavaScript, więc można sobie wyobrazić, że dzieje się to równolegle z potencjalnym wykonaniem jakiegoś kodu JavaScript. Ale podane wywołanie zwrotne setTimeoutmoże zostać wykonane tylko wtedy, gdy aktualnie wykonywany kod JavaScript dobiegnie końca i odczytywana jest odpowiednia kolejka zdarzeń.

  • fetch(): agent użyje funkcji systemu operacyjnego do wykonania żądania HTTP i monitorowania każdej przychodzącej odpowiedzi. Ponownie to zadanie inne niż JavaScript może działać równolegle z niektórymi kodami JavaScript, które są nadal wykonywane. Ale procedura rozstrzygania obietnicy, która rozwiąże obietnicę zwróconą przez fetch(), może zostać wykonana tylko wtedy, gdy aktualnie wykonywany skrypt JavaScript dobiegnie końca.

  • requestAnimationFrame(): silnik renderujący przeglądarki (inny niż JavaScript) umieści zdarzenie w kolejce JavaScript, gdy będzie gotowy do wykonania operacji malowania. Podczas przetwarzania zdarzenia JavaScript wykonywana jest funkcja zwrotna.

  • queueMicrotask(): natychmiast umieszcza zdarzenie w kolejce do wykonywania wielu zadań. Oddzwanianie zostanie wykonane, gdy stos wywołań będzie pusty, a zdarzenie zostanie zużyte.

Istnieje wiele innych przykładów, ale wszystkie te funkcje są dostarczane przez środowisko hosta, a nie przez rdzeń EcmaScript. Dzięki rdzeniu EcmaScript możesz synchronicznie umieszczać zdarzenie w kolejce Promise Job Promise.resolve().

Konstrukcje językowe

ECMAScript oferuje kilka konstrukcji językowych w celu wspierania wzór asynchrony, takie jak yield, async, await. Ale niech się nie pomyli: żaden kod JavaScript nie zostanie przerwany przez zdarzenie zewnętrzne. W „przerwa”, które yieldi awaitwydają się zapewniać tylko kontrolowane, predefiniowane sposób powrocie z wywołania funkcji i przywracając jej kontekst wykonywania później, albo za pomocą kodu JS (w przypadku yield), albo kolejki zdarzeń (w przypadku await).

Obsługa zdarzeń DOM

Gdy kod JavaScript uzyskuje dostęp do interfejsu API DOM, może to w niektórych przypadkach spowodować, że interfejs API DOM wyzwoli jedno lub więcej powiadomień synchronicznych. A jeśli twój kod nasłuchuje zdarzenia, zostanie wywołany.

Może się to wydawać wyprzedzającą współbieżnością, ale tak nie jest: po zwróceniu procedur obsługi zdarzeń DOM API w końcu również zwróci i oryginalny kod JavaScript będzie kontynuowany.

W innych przypadkach DOM API po prostu wywoła zdarzenie w odpowiedniej kolejce zdarzeń, a JavaScript wykryje je po opróżnieniu stosu wywołań.

Zobacz zdarzenia synchroniczne i asynchroniczne


0

Jest synchroniczny we wszystkich przypadkach.

Przykład blokowania wątku za pomocą Promises:

  const test = () => new Promise((result, reject) => {
    const time = new Date().getTime() + (3 * 1000);

    console.info('Test start...');

    while (new Date().getTime() < time) {
      // Waiting...
    }

    console.info('Test finish...');
  });

  test()
    .then(() => console.info('Then'))
    .finally(() => console.info('Finally'));

  console.info('Finish!');

Dane wyjściowe będą:

Test start...
Test finish...
Finish!
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.