Dlaczego setTimeout (fn, 0) jest czasem przydatny?

Ostatnio natknąłem się na dość paskudny błąd, w którym kod ładował <select> dynamicznie przez JavaScript. Ten dynamicznie załadowany <select> miał wstępnie wybraną wartość. W IE6 mieliśmy już kod naprawiający wybrany <option>, ponieważ czasami wartość <select> s selectedIndex nie byłaby zsynchronizowana z wybranym atrybutem <option> s index, jak poniżej:

field.selectedIndex = element.index;

Jednak ten kod nie działał. Mimo, że pole selectedIndex było ustawione poprawnie, błędny indeks byłby wybrane. Jeśli jednak wpiszę alert() oświadczenie we właściwym czasie, zostanie wybrana właściwa opcja. Myśląc, że to może być jakiś problem z czasem, próbowałem czegoś losowego, co widziałem wcześniej w kodzie: {]}

var wrapFn = (function() {
    var myField = field;
    var myElement = element;

    return function() {
        myField.selectedIndex = myElement.index;
    }
})();
setTimeout(wrapFn, 0);

I to zadziałało!

Mam rozwiązanie mojego problemu, ale jestem zaniepokojony, że nie wiem dokładnie, dlaczego to rozwiązuje mój problem. Czy ktoś ma oficjalne wyjaśnienie? Jakiego problemu z przeglądarką unikam, wywołując moją funkcję "później" za pomocą setTimeout()?

Author: refactor, 2009-04-23

17 answers

To działa, ponieważ wykonujesz wielozadaniowość współpracy.

Przeglądarka musi wykonać wiele rzeczy na raz, a jedną z nich jest execute JavaScript. Ale jedną z rzeczy, do których JavaScript jest bardzo często używany, jest poproszenie przeglądarki o zbudowanie elementu wyświetlania. Często zakłada się, że odbywa się to synchronicznie (szczególnie, że JavaScript nie jest wykonywany równolegle), ale nie ma gwarancji, że tak jest i JavaScript nie ma dobrze zdefiniowanego mechanizmu dla czekam.

Rozwiązaniem jest "wstrzymanie" wykonania JavaScript, aby pozwolić wątkom renderującym nadrobić zaległości. I jest to efekt, który setTimeout() z czasem 0 tak. To jest jak wątek / wydajność procesu w C. Chociaż wydaje się powiedzieć "uruchom to natychmiast" to faktycznie daje przeglądarce szansę, aby zakończyć robi pewne rzeczy nie-JavaScript, które czekały na zakończenie przed udziałem w tym nowym kawałku JavaScript.

(w rzeczywistości, setTimeout() ponownie kolejkuje Nowy JavaScript na koniec kolejki egzekucji. W komentarzach znajdują się linki do dłuższych wyjaśnień.)

IE6 tak się składa, że jest bardziej podatny na ten błąd, ale widziałem go na starszych wersjach Mozilli i w Firefoksie.

Zobacz rozmowę Philipa Robertsa "Co to do cholery jest pętla zdarzeń?" dla dokładniejszego wyjaśnienia.

 685
Author: staticsan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-03 16:42:13

Przedmowa:

WAŻNA UWAGA: chociaż jest to najbardziej upvoted i akceptowane, przyjęta odpowiedź przez @ staticsan faktycznie jest nie poprawne! - zobacz komentarz Davida Muldera, aby wyjaśnić dlaczego.

Niektóre z innych odpowiedzi są poprawne, ale w rzeczywistości nie ilustrują, na czym polega rozwiązywany problem, więc stworzyłem tę odpowiedź, aby przedstawić tę szczegółową ilustrację.

W związku z tym zamieszczam szczegółowy przegląd tego, co robi przeglądarka i jak używanie setTimeout() pomaga . Wygląda na długi, ale w rzeczywistości jest bardzo prosty i prosty - zrobiłem go bardzo szczegółowo.

UPDATE: I have made a JSFiddle to live-zademonstrować Wyjaśnienie poniżej: http://jsfiddle.net/C2YBE/31 /. Wiele dzięki dla @ThangChung za pomoc w uruchomieniu go.

UPDATE2: na wypadek, gdyby strona JSFiddle umarła lub usunęła kod, dodałem kod do tej odpowiedzi na bardzo koniec.


Szczegóły:

Wyobraź sobie aplikację internetową z przyciskiem "zrób coś" I div wyniku.

Obsługa onClick przycisku "zrób coś "wywołuje funkcję" LongCalc ()", która wykonuje 2 rzeczy:

  1. Wykonuje bardzo długie obliczenia (powiedzmy 3 min)

  2. Wypisuje wyniki obliczeń do div wyniku.

Teraz, twoi użytkownicy zaczynają to testować, kliknij przycisk" Zrób coś", a strona siedzi nie robi pozornie nic przez 3 minuty, stają się niespokojni, kliknij przycisk ponownie, poczekaj 1 min, nic się nie dzieje, kliknij przycisk ponownie...

Problem jest oczywista - chcesz DIV "Status", który pokazuje, co się dzieje. Zobaczmy, jak to działa.


Więc dodajesz "status" DIV (początkowo pusty) i modyfikujesz onclick handler (function LongCalc()), aby zrobić 4 rzeczy:

  1. Uzupełnij status " Obliczanie... może potrwać ~3 minuty " do stanu DIV

  2. Wykonuje bardzo długie obliczenia (powiedzmy 3 min)

  3. Wypisuje wyniki obliczeń do div wyniku.

  4. Wypełnianie statusu "wykonanie obliczeń" do statusu DIV

I z radością dajesz aplikację użytkownikom do ponownego przetestowania.

Wracają do ciebie wyglądając na bardzo wściekłych. I wyjaśnij, że kiedy kliknęli przycisk, DIV statusu nigdy nie został zaktualizowany o "obliczanie..." status!!!


[29]} podrapujesz się po głowie, popytasz na StackOverflow (lub czytasz dokumenty lub google) i zdajesz sobie sprawę z problemu: [34]} Przeglądarka umieszcza wszystkie swoje zadania "TODO" (zarówno zadania interfejsu użytkownika, jak i polecenia JavaScript) wynikające z zdarzeń w pojedynczej kolejce. I niestety, ponowne rysowanie DIV "Status"z nowym" Obliczanie..."wartość jest oddzielnym TODO, które przechodzi do końca kolejki!

Oto zestawienie wydarzeń podczas testu użytkownika, zawartość kolejki po każdym zdarzeniu:

  • Kolejka: [Empty]
  • Zdarzenie: kliknij przycisk. Kolejka po zdarzeniu: [Execute OnClick handler(lines 1-4)]
  • Event: wykonanie pierwszej linii w programie obsługi OnClick (np. zmiana wartości DIV statusu). Kolejka po zdarzeniu: [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value]. należy pamiętać, że gdy zmiany DOM następują natychmiastowo, aby ponownie narysować odpowiedni element DOM, potrzebne jest nowe zdarzenie, wywołane przez zmianę DOM, która nastąpiła na końcu kolejki .
  • PROBLEM!!! PROBLEM!!! szczegóły wyjaśnione poniżej.
  • Event: wykonanie drugiej linii w handlerze (obliczenia). Kolejka po: [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value].
  • Event: Uruchom 3. linię w module obsługi (uzupełnij wynik DIV). Kolejka po: [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result].
  • Event: Execute 4th line in handler (wypełnij DIV statusem "DONE"). Kolejka: [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value].
  • Event: execute implikated return from onclick handler sub. Usuwamy z kolejki" Execute OnClick handler " i rozpoczynamy wykonywanie następnego elementu na Kolejka
  • UWAGA: Ponieważ skończyliśmy już obliczenia, dla użytkownika minęły już 3 minuty. ponowne losowanie jeszcze się nie wydarzyło!!!
  • Event: re-draw status DIV z wartością" Calculating". Robimy ponowne losowanie i zdejmujemy to z kolejki.
  • Event: ponownie narysuj DIV wyniku z wartością wyniku. Robimy ponowne losowanie i zdejmujemy to z kolejki.
  • Event: re-draw status DIV z wartością "Done". Robimy ponowne losowanie i zdejmujemy to z kolejki. Widzowie o ostrych oczach może nawet zauważyć "status DIV z wartością "obliczającą" migającą dla ułamka mikrosekundy - po zakończeniu obliczeń

Tak więc, podstawowym problemem jest to, że zdarzenie ponownego losowania dla" Status "DIV jest umieszczane w kolejce na końcu, po zdarzeniu "wykonaj linię 2", które trwa 3 minuty, więc rzeczywiste ponowne losowanie nie nastąpi przed wykonaniem obliczeń.


Na ratunek przychodzi setTimeout(). Jak to pomaga? Ponieważ przez wywołanie long-executing kod poprzez setTimeout, tworzysz 2 zdarzenia: setTimeout samo wykonanie i (ze względu na 0 timeout), oddzielny wpis kolejki dla wykonywanego kodu.

Więc, aby rozwiązać twój problem, zmodyfikuj swój onClick handler na dwa polecenia (w nowej funkcji lub tylko bloku wewnątrz onClick):

  1. Uzupełnij status " Obliczanie... może potrwać ~3 minuty " do statusu DIV

  2. Wykonaj setTimeout() z 0 timeoutem i wywołaniem LongCalc() funkcji.

    LongCalc() funkcja jest prawie taka sama jak ostatnio, ale oczywiście nie ma " obliczania..."status DIV aktualizacja jako pierwszy krok; i zamiast tego rozpoczyna obliczenia od razu.

Jak wygląda teraz kolejność zdarzeń i Kolejka?

  • Kolejka: [Empty]
  • Zdarzenie: kliknij przycisk. Kolejka po zdarzeniu: [Execute OnClick handler(status update, setTimeout() call)]
  • Event: wykonanie pierwszej linii w programie obsługi OnClick (np. zmiana wartości DIV statusu). Kolejka po zdarzeniu: [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value].
  • Event: wykonanie drugiej linii w handlerze (wywołanie setTimeout). Kolejka po: [re-draw Status DIV with "Calculating" value]. Kolejka nie ma nic nowego w nim przez 0 sekund więcej.
  • Event: Alarm z timeout gaśnie, 0 sekund później. Kolejka po: [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)].
  • Event: ponownie narysuj DIV statusu z wartością "obliczającą" . Kolejka po: [execute LongCalc (lines 1-3)]. Należy pamiętać, że to zdarzenie ponownego losowania może mieć miejsce przed włączeniem alarmu, co działa równie dobrze.
  • ...
Hura! Status DIV właśnie został zaktualizowany do " Obliczanie..."przed rozpoczęciem obliczeń!!!

Poniżej znajduje się przykładowy kod z JSFiddle ilustrujący te przykłady: http://jsfiddle.net/C2YBE/31/ :

Kod HTML:

<table border=1>
    <tr><td><button id='do'>Do long calc - bad status!</button></td>
        <td><div id='status'>Not Calculating yet.</div></td>
    </tr>
    <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
        <td><div id='status_ok'>Not Calculating yet.</div></td>
    </tr>
</table>

Kod JavaScript: (wykonywany na onDomReady i może wymagać jQuery 1.9)

function long_running(status_div) {

    var result = 0;
    // Use 1000/700/300 limits in Chrome, 
    //    300/100/100 in IE8, 
    //    1000/500/200 in FireFox
    // I have no idea why identical runtimes fail on diff browsers.
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    $(status_div).text('calculation done');
}

// Assign events to buttons
$('#do').on('click', function () {
    $('#status').text('calculating....');
    long_running('#status');
});

$('#do_ok').on('click', function () {
    $('#status_ok').text('calculating....');
    // This works on IE8. Works in Chrome
    // Does NOT work in FireFox 25 with timeout =0 or =1
    // DOES work in FF if you change timeout from 0 to 500
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
});
 571
Author: DVK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-27 08:18:20

Spójrz na artykuł Johna Resiga o Jak działają timery JavaScript. Kiedy ustawisz timeout, to faktycznie kolejkuje kod asynchroniczny, aż silnik wykona bieżący stos wywołań.

 84
Author: Andy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-06 21:38:30

setTimeout() kupuje ci trochę czasu do załadowania elementów DOM, nawet jeśli jest ustawione na 0.

Zobacz to: setTimeout

 20
Author: Jose Basilio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-22 21:52:10

Większość przeglądarek posiada proces o nazwie main thread, który jest odpowiedzialny za wykonywanie niektórych zadań JavaScript, aktualizacje interfejsu użytkownika np.: malowanie, przerysowywanie lub reflow, itp.

Niektóre zadania związane z wykonywaniem JavaScript i aktualizacją interfejsu użytkownika są kolejkowane do kolejki komunikatów przeglądarki, a następnie wysyłane do głównego wątku przeglądarki, który ma zostać wykonany.

Gdy aktualizacje interfejsu użytkownika są generowane, gdy główny wątek jest zajęty, zadania są dodawane do kolejki komunikatów.

setTimeout(fn, 0); dodaj to fn na koniec kolejki do zostać stracony. Ustawia zadanie, które ma być dodane do kolejki komunikatów po określonym czasie.

 20
Author: arley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-09 13:03:27

Istnieją sprzeczne, upvoted odpowiedzi tutaj, i bez dowodu nie ma sposobu, aby wiedzieć, komu wierzyć. Oto dowód, że @ DVK ma rację, a @ SalvadorDali się myli. Ten ostatni twierdzi:

" i oto dlaczego: nie można mieć setTimeout z czasem opóźnienie 0 milisekund. Wartość minimalna jest określona przez przeglądarka i nie jest to 0 milisekund. Historycznie przeglądarki ustawiają to minimum 10 milisekund, ale specyfikacje HTML5 i nowoczesne przeglądarki weź to. Ustaw na 4 milisekundy."

Minimalny limit czasu 4ms nie ma znaczenia dla tego, co się dzieje. To, co tak naprawdę się dzieje, to to, że setTimeout popycha funkcję callback do końca kolejki wykonawczej. Jeśli po setTimeout (callback, 0) masz kod blokujący, który trwa kilka sekund, aby uruchomić, callback nie zostanie wykonany przez kilka sekund, dopóki kod blokujący nie zakończy się. Wypróbuj ten kod:

function testSettimeout0 () {
    var startTime = new Date().getTime()
    console.log('setting timeout 0 callback at ' +sinceStart())
    setTimeout(function(){
        console.log('in timeout callback at ' +sinceStart())
    }, 0)
    console.log('starting blocking loop at ' +sinceStart())
    while (sinceStart() < 3000) {
        continue
    }
    console.log('blocking loop ended at ' +sinceStart())
    return // functions below
    function sinceStart () {
        return new Date().getTime() - startTime
    } // sinceStart
} // testSettimeout0

Wyjście To:

setting timeout 0 callback at 0
starting blocking loop at 5
blocking loop ended at 3000
in timeout callback at 3033
 17
Author: Vladimir Kornea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-07 20:21:46

Jednym z powodów jest odroczenie wykonania kodu do oddzielnej, późniejszej pętli zdarzeń. W odpowiedzi na jakieś zdarzenie przeglądarki (na przykład kliknięcie myszką) czasami konieczne jest wykonanie operacji tylko po przetworzeniu bieżącego zdarzenia. setTimeout() jest najprostszym sposobem na to.

edit Teraz, gdy jest 2015 powinienem zauważyć, że jest też requestAnimationFrame(), co nie jest dokładnie takie samo, ale jest wystarczająco blisko setTimeout(fn, 0), że warto wspominam.

 12
Author: Pointy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-22 12:41:06

To stare pytania ze starymi odpowiedziami. Chciałem dodać nowe spojrzenie na ten problem i odpowiedzieć, dlaczego tak się dzieje, a nie dlaczego jest to przydatne.

Więc masz dwie funkcje:

var f1 = function () {    
   setTimeout(function(){
      console.log("f1", "First function call...");
   }, 0);
};

var f2 = function () {
    console.log("f2", "Second call...");
};

A następnie wywołaj je w następującej kolejności f1(); f2();, aby zobaczyć, że drugi wykonał pierwszy.

I oto dlaczego: nie jest możliwe posiadanie setTimeout z opóźnieniem czasowym wynoszącym 0 milisekund. Minimalna wartość jest określana przez przeglądarkę {[11] } i nie wynosi 0 milisekund. historycznie przeglądarki ustawiają to minimum na 10 milisekund, ale specyfikacje HTML5 i współczesne przeglądarki mają to ustawione na 4 milisekundy.

Jeśli poziom zagnieżdżania jest większy niż 5, a limit czasu jest mniejszy niż 4, to zwiększ limit czasu do 4.

Również z Mozilli:

Aby zaimplementować limit czasu 0 ms w nowoczesnej przeglądarce, możesz użyć okno.postMessage() jak opisano tutaj .

P. S. informacja jest pobierana po czytanie następującego artykułu .

 9
Author: Salvador Dali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-19 21:34:47

Ponieważ jest on przekazywany na czas 0, przypuszczam, że jest to w celu usunięcia kodu przekazanego do setTimeout z przepływu wykonania. Jeśli więc jest to funkcja, która może chwilę potrwać, nie uniemożliwi to wykonania kolejnego kodu.

 8
Author: user113716,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-01 17:39:29

Inną rzeczą, która to robi, jest przesunięcie wywołania funkcji na dno stosu, zapobiegając przepełnieniu stosu, jeśli wywołujesz funkcję rekurencyjnie. Ma to efekt pętli while, ale pozwala silnikowi JavaScript uruchamiać inne asynchroniczne timery.

 3
Author: Jason Suárez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-21 01:14:43

Odpowiedzi na temat pętli wykonawczych i renderowania DOM przed ukończeniem innego kodu są poprawne. Zero second timeouts w JavaScript pomaga uczynić kod pseudo-wielowątkowy, nawet jeśli tak nie jest.

Chcę dodać, że najlepsza wartość dla cross browser / cross platform zero - Second timeout w JavaScript jest w rzeczywistości około 20 milisekund zamiast 0 (zero), ponieważ wiele przeglądarek mobilnych nie może zarejestrować timeoutów mniejszych niż 20 milisekund z powodu ograniczeń zegara na AMD chipsy.

Również długotrwałe procesy, które nie wymagają manipulacji DOM, powinny być teraz wysyłane do pracowników sieci, ponieważ zapewniają one prawdziwe wielowątkowe wykonywanie JavaScript.

 2
Author: ChrisN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-13 23:29:55

Wywołując setTimeout dajesz stronie Czas na reakcję na to, co robi użytkownik. Jest to szczególnie przydatne w przypadku funkcji uruchamianych podczas ładowania strony.

 1
Author: Jeremy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-22 21:53:43

Inne przypadki, w których setTimeout jest przydatny:

Chcesz przerwać długo działającą pętlę lub obliczenia na mniejsze komponenty, aby przeglądarka nie wyglądała na "zamrożoną" lub nie powiedziała "skrypt na stronie jest zajęty".

Chcesz wyłączyć przycisk Wyślij formularz po kliknięciu, ale jeśli wyłączysz przycisk w programie obsługi onClick, formularz nie zostanie przesłany. setTimeout z czasem zerowym robi sztuczkę, pozwalając na zakończenie wydarzenia, rozpoczęcie składania formularza, wtedy twój przycisk może być niepełnosprawni.

 1
Author: fabspro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-14 13:09:00

SetTimout na 0 jest również bardzo przydatny we wzorze tworzenia odroczonej obietnicy, którą chcesz natychmiast zwrócić:

myObject.prototype.myMethodDeferred = function() {
    var deferredObject = $.Deferred();
    var that = this;  // Because setTimeout won't work right with this
    setTimeout(function() { 
        return myMethodActualWork.call(that, deferredObject);
    }, 0);
    return deferredObject.promise();
}
 1
Author: Stephan G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-02 15:39:58

Obie te dwie Najwyżej oceniane odpowiedzi są błędne. Sprawdź opis MDN na modelu współbieżności i pętli zdarzeń, i powinno być jasne, co się dzieje (ten zasób MDN jest prawdziwym klejnotem). I po prostu używając setTimeout może być dodanie nieoczekiwanych problemów w kodzie oprócz "rozwiązania" tego małego problemu.

Co tu się dzieje w zasadzie Nie chodzi o to, że "przeglądarka może nie być jeszcze gotowa, bo nie jest gotowa", czy coś w oparciu na "każda linia jest zdarzeniem, które zostanie dodane do tyłu kolejki".

Jsfiddle dostarczone przez DVK rzeczywiście ilustruje problem, ale jego wyjaśnienie nie jest poprawne.

W jego kodzie dzieje się to, że najpierw dołącza obsługę zdarzenia do zdarzenia click na przycisku #do.

Następnie, po kliknięciu przycisku, tworzony jest message odwołujący się do funkcji obsługi zdarzeń, która jest dodawana do message queue. Kiedy event loop osiągnie to message, tworzy frame na stosie, z wywołaniem funkcji do obsługi zdarzenia click w jsfiddle.

I tu robi się ciekawie. Jesteśmy tak przyzwyczajeni do myślenia o Javascript jako asynchronicznym, że jesteśmy skłonni przeoczyć ten drobny fakt: każda ramka musi zostać wykonana, w całości, zanim Następna ramka może być wykonana. Bez sprzeczności, ludzie. Co to znaczy? Oznacza to, że za każdym razem, gdy funkcja jest wywoływana z kolejki komunikatów, blokuje kolejka do czasu, aż wygenerowany stos zostanie opróżniony. Lub, mówiąc bardziej ogólnie, blokuje do momentu powrotu funkcji. I blokuje Wszystko , w tym operacje renderowania DOM, przewijanie i tak dalej. Jeśli chcesz potwierdzić, po prostu spróbuj wydłużyć czas trwania długotrwałej operacji w fiddle (np. Uruchom pętlę zewnętrzną jeszcze 10 razy), a zauważysz, że podczas jej działania nie możesz przewijać strony. Jeśli działa wystarczająco długo, twoja przeglądarka zapyta cię, czy chcesz aby zabić proces, ponieważ sprawia, że strona nie reaguje. Ramka jest wykonywana, a pętla zdarzeń i Kolejka komunikatów zostają zablokowane do jej zakończenia.

Więc dlaczego ten efekt uboczny tekstu nie aktualizuje? Ponieważ podczas gdy zmieniono wartość elementu w DOM - możesz {[7] } natychmiast po jego zmianie i zobaczyć, że została zmieniona (co pokazuje, dlaczego Wyjaśnienie DVK nie jest poprawne) - przeglądarka czeka, aż stos zostanie zmieniony. deplete (zwracana funkcja obsługi on) i tym samym wiadomość do końca, tak aby mogła w końcu przejść do wykonania wiadomości, która została dodana przez runtime jako reakcja na naszą operację mutacji i w celu odzwierciedlenia tej mutacji w interfejsie użytkownika.

Dzieje się tak dlatego, że w rzeczywistości czekamy na zakończenie działania kodu. Nie powiedzieliśmy "ktoś to pobrać, a następnie wywołać tę funkcję z wynikami, Dzięki, a teraz mam dość, więc Imma powrót, zrób co chcesz", jak my Zwykle wykonuj z naszym asynchronicznym Javascript opartym na zdarzeniach. Wprowadzamy funkcję obsługi zdarzenia click, aktualizujemy element DOM, wywołujemy inną funkcję, druga funkcja działa przez długi czas, a następnie powraca, aktualizujemy ten sam element DOM i następnie wracamy z początkowej funkcji, skutecznie opróżniając stos. I wtedy przeglądarka może dostać się do następnej wiadomości w kolejce, która bardzo dobrze może być wiadomością wygenerowaną przez nas poprzez wywołanie jakiegoś wewnętrznego zdarzenie typu "on-DOM-mutation".

Interfejs przeglądarki nie może (lub nie chce) aktualizować interfejsu użytkownika do czasu zakończenia aktualnie wykonywanej ramki (funkcja powróciła). Osobiście uważam, że jest to raczej projekt niż ograniczenie.

Dlaczego więc to działa? Robi to, ponieważ skutecznie usuwa wywołanie funkcji long-running z własnej ramki, planując jej wykonanie później w kontekście window, tak aby ona sama mogła zwrócić natychmiast {[20] } i pozwól kolejce komunikatów przetwarzać inne wiadomości. Chodzi o to, że komunikat UI "on update", który został uruchomiony przez nas w Javascript podczas zmiany tekstu w DOM, jest teraz przed Komunikatem kolejkowanym do długo działającej funkcji, tak że Aktualizacja UI dzieje się zanim zablokujemy przez długi czas.

Zauważ, że a) długo działająca funkcja nadal blokuje Wszystko, gdy działa, i b) nie masz gwarancji, że aktualizacja interfejsu jest rzeczywiście przed to w kolejce wiadomości. W mojej przeglądarce Chrome z czerwca 2018 r. wartość 0 nie "naprawia" problemu, który demonstruje fiddle - robi to 10. W rzeczywistości jestem trochę przytłumiony tym, ponieważ wydaje mi się logiczne, że Komunikat aktualizacji interfejsu użytkownika powinien być ustawiony w kolejce przed nim, ponieważ jego wyzwalacz jest wykonywany przed zaplanowaniem długo działającej funkcji, która ma być uruchomiona "później". Ale być może są jakieś optymalizacje w silniku V8, które mogą przeszkadzać, a może po prostu brakuje mi zrozumienia.

Ok, więc jaki jest problem z używaniem setTimeout i jakie jest lepsze rozwiązanie w tym konkretnym przypadku?

Po pierwsze, problem z używaniem setTimeout Na każdym takim procederze obsługi zdarzeń, aby złagodzić inny problem, jest podatny na bałagan z innym kodem. Oto prawdziwy przykład z mojej pracy:

Kolega, w błędnym zrozumieniu pętli zdarzeń, próbował "wątek" Javascript poprzez użycie kodu renderującego szablon setTimeout 0 do jego renderowania. Nie ma go tu, żeby pytać, ale ja ... można przypuszczać, że być może wstawił timery, aby zmierzyć szybkość renderowania (która byłaby zwrotnością funkcji) i odkrył, że użycie tego podejścia spowodowałoby błyskawiczne odpowiedzi z tej funkcji.

Pierwszy problem jest oczywisty; nie możesz wątku javascript, więc nic nie wygrasz, dodając zaciemnienie. Po drugie, teraz skutecznie oddzieliłeś renderowanie szablonu od stosu możliwych detektorów zdarzeń, które mogą oczekiwać, że ten sam szablon zostały wyrenderowane, choć może nie były. Rzeczywiste zachowanie tej funkcji było teraz niedeterministyczne, podobnie jak - nieświadomie-każda funkcja, która by ją uruchamiała lub od niej zależała. Można zgadywać, ale nie można poprawnie zakodować jego zachowania.

" fix " podczas pisania nowej obsługi zdarzeń, która zależała od jej logiki, polegało na również używaniu setTimeout 0. Ale to nie jest poprawka, trudno to zrozumieć i nie jest zabawne debugować błędy, które są spowodowane przez taki kod. Czasami nie ma problemu nigdy, innym razem to consistently zawodzi, a potem znowu, czasami działa i łamie sporadycznie, w zależności od aktualnej wydajności platformy i co jeszcze dzieje się w tym czasie. Dlatego osobiście odradzałbym używanie tego hacka (it is a hack, and we all should know that it is), chyba że naprawdę wiesz, co robisz i jakie są konsekwencje.

Ale co możemy zrobić zamiast tego? Cóż, jak sugeruje wspomniany artykuł MDN, albo podziel pracę na wiele wiadomości (Jeśli możesz), aby inne wiadomości, które są w kolejce, mogły być przeplatane z Twoją pracą i wykonywane podczas jej działania, lub użyj pracownika sieci, który może działać równolegle z Twoją stroną i zwracać wyniki po wykonaniu jego obliczeń.

A jeśli myślisz: "cóż, nie mógłbym po prostu umieścić wywołania zwrotnego w funkcji long-running, aby uczynić ją asynchroniczną?"w takim razie nie. Oddzwanianie nie robi jest asynchroniczny, nadal będzie musiał uruchomić długo działający kod przed jawnym wywołaniem połączenia zwrotnego.
 1
Author: DanielSmedegaardBuus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-05 12:43:35

Javascript jest jednowątkową aplikacją, która nie pozwala na jednoczesne uruchamianie funkcji, więc do osiągnięcia tego zdarzenia używa się pętli. Więc dokładnie co setTimeout (fn, 0) zrobić, że jego pussed do zadania quest, który jest wykonywany, gdy stos wywołań jest pusty. Wiem, że to wyjaśnienie jest dość nudne, więc polecam przejrzeć ten film, który pomoże Ci w tym, jak rzeczy działają pod maską w przeglądarce. Zobacz ten film:- https://www.youtube.com/watch?time_continue=392&v=8aGhZQkoFbQ

 0
Author: Jani Devang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-02 07:49:06

Problem polegał na tym, że próbowałeś wykonać operację Javascript na nieistniejącym elemencie. Element nie został jeszcze załadowany i setTimeout() daje więcej czasu na załadowanie elementu w następujący sposób:

  1. setTimeout() powoduje, że zdarzenie jest ansynchronous dlatego jest wykonywane po całym kodzie synchronicznym, dając twojemu elementowi więcej czasu na załadowanie. Asynchroniczne wywołania zwrotne, takie jak wywołanie zwrotne w setTimeout() są umieszczane w kolejce zdarzeń i umieszczane na stosie przez pętla zdarzeń po tym, jak stos kodu synchronicznego jest pusty.
  2. wartość 0 dla ms jako drugiego argumentu w funkcji setTimeout() jest często nieco wyższa(4-10ms w zależności od przeglądarki). Ten nieco wyższy czas potrzebny na wykonanie wywołania zwrotnego setTimeout() jest spowodowany ilością "kleszczy" (gdzie kleszcz wypycha wywołanie zwrotne na stosie, jeśli stos jest pusty) pętli zdarzeń. Ze względu na wydajność i żywotność baterii ilość kleszczy w pętli zdarzeń jest ograniczona do pewnego ilość mniejsza niż 1000 razy na sekundę.
 0
Author: Willem van der Veen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-15 09:12:40