Dlaczego setTimeout () powoduje, że moja aplikacja jest opóźniona, a Rxjs timer (). Subscribe (…) nie?

9

Mam komponent, który „leniwie ładuje” kilka komentarzy w odstępie 100ms.

Kiedy używam setTimeout, jest to naprawdę opóźnione.

składnik

<div *ngFor="let post of posts">
   <app-post [post]="post" ></app-post>
</div>

To sprawia, że ​​moja aplikacja jest opóźniona (średni fps 14, czas bezczynności 51100 ms):

while(this.postService.hasPosts()){
  setTimeout(()=> {
   this.posts.push(this.postService.next(10));
  },100);
}

To sprawia, że ​​moja aplikacja jest płynna (średni fps 35, czas bezczynności 40800 ms)

while(this.postService.hasPosts()){
  timer(100).subscribe(()=> {
    this.posts.push(this.postService.next(10));
  });
}

Czy jest jakieś wytłumaczenie, dlaczego zegar rxjs działa o wiele lepiej?

Przeprowadziłem analizę środowiska uruchomieniowego za pomocą Firefoxa. W pierwszym przykładzie liczba klatek na sekundę spada do 14 fps W drugim przykładzie 35 fps.

Nawet czas bezczynności jest o 20% krótszy.

Ta metoda jest jeszcze bardziej płynna (śr. Kl./s 45, czas bezczynności 13500 ms):

interval(100).pipe(takeWhile(this.postService.hasPosts()).subscribe(()=> {
    this.posts.push(this.postService.next(10));
  });
}
Luxusproblem
źródło

Odpowiedzi:

2

Twoje ostatnie rozwiązanie jest jedyne prawidłowe.

Pozostałe dwa rozwiązania nie powinny działać tak, jak się spodziewałeś. W rzeczywistości powinno to doprowadzić do nieskończonej pętli.

Wynika to z tego, jak działa Eventloop JavaScript . Poniższe zdjęcie pokazuje model środowiska wykonawczego JavaScript (Zdjęcie zostało zrobione stąd ):

wprowadź opis zdjęcia tutaj

Odpowiednie dla nas części to stacki queue. Środowisko wykonawcze JavaScript przetwarza wiadomości w queue. Każda wiadomość jest powiązana z funkcją, która jest wywoływana podczas przetwarzania wiadomości.

Dla stosu każde wywołanie funkcji tworzy ramkę na stosie, która zawiera argumenty funkcji i zmienne lokalne. Jeśli funkcja wywołuje inną funkcję, nowa ramka jest umieszczana na stosie. Kiedy funkcja zwraca, górna ramka jest wyskakiwana ze stosu.

Teraz, jeśli stos jest pusty, środowisko wykonawcze JavaScript przetworzy następną wiadomość na queue(najstarszym).

Jeśli użyjesz setTimeout(() => doSomething(),100), doSomething()funkcja zostanie dodana do kolejki po 100 milisekundach. To jest powód, dla którego 100 milisekund nie jest gwarantowanym czasem, ale czasem minimalnym. Dlatego twoja doSomething methodjest wywoływana tylko wtedy, gdy stos jest pusty i nic więcej nie znajduje się w kolejce.

Ale gdy iterujesz w pętli while, a twój stan zależy od kodu wewnątrz setTimeout, stworzyłeś nieskończoną pętlę, ponieważ stos nie będzie pusty i dlatego twój this.posts.push(this.postService.next(10));kod nigdy nie zostanie wywołany.

To samo dotyczy implementacji RxJS. Używają harmonogramów do obsługi czasu. Istnieją różne implementacje wewnętrznego harmonogramu w RxJS, ale jak widzimy w implementacjach dla intervali timer, jeśli nie określimy harmonogramu, domyślnym jest asyncScheduler. Harmonogramy asyncScheduler działają z tymi, setIntervalktóre działają jak setTimeoutwspomniano powyżej, i wypychają kolejny komunikat do kolejki.

Wypróbowałem twoje dwa rozwiązania z pętlą while i tak naprawdę pierwsze całkowicie zamroziło moją przeglądarkę, podczas gdy drugie było bardzo opóźnione, ale mogło wysyłać coś do konsoli wewnątrz pętli while. Właściwie nie wiem, dlaczego ten drugi jest nieco bardziej wydajny, ale mimo to oba nie są tym, czego naprawdę chcesz. Znalazłeś już dobre rozwiązanie i mam nadzieję, że ta odpowiedź pomoże ci zrozumieć, dlaczego pierwsze rozwiązania działają tak źle.

Max K.
źródło