Почему setTimeout () делает мое приложение медленным, а Rxjs timer (). Subscribe (…) - нет?

9

У меня есть компонент, который «лениво загружает» некоторые комментарии с интервалом в 100 мс.

Когда я использую setTimeout, это действительно лагает.

составная часть

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

Это делает мое приложение медленным (среднее число кадров в секунду 14, время простоя 51100 мс):

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

Это делает мое приложение гладким (в среднем 35 кадров в секунду, время простоя 40800 мс)

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

Есть ли объяснение, почему таймер rxjs работает так лучше?

Я сделал анализ времени выполнения с Firefox. В первом примере частота кадров падает до 14 кадров в секунду. В другом примере 35 кадров в секунду.

Даже простой на 20% ниже.

Этот метод еще более плавный (среднее значение fps 45, время простоя 13500 мс):

interval(100).pipe(takeWhile(this.postService.hasPosts()).subscribe(()=> {
    this.posts.push(this.postService.next(10));
  });
}
Luxusproblem
источник

Ответы:

2

Ваше последнее решение - единственно правильное.

Два других решения не должны работать так, как вы ожидали. На самом деле, это должно привести к бесконечному циклу.

Это из-за того, как работает Eventloop JavaScript . На следующем рисунке показана модель среды выполнения JavaScript (изображение было взято отсюда ):

введите описание изображения здесь

Для нас важными частями являются stackи queue. Среда выполнения JavaScript обрабатывает сообщения в queue. Каждое сообщение связано с функцией, которая вызывается при обработке сообщения.

Для стека каждый вызов функции создает в стеке фрейм, содержащий аргументы функций и локальные переменные. Если функция вызывает другую функцию, новый кадр помещается поверх стека. Когда функция возвращает верхний кадр, он выталкивается из стека.

Теперь, если стек пуст, среда выполнения JavaScript будет обрабатывать следующее сообщение queue(самое старое).

Если вы используете setTimeout(() => doSomething(),100), doSomething()функция добавляется в очередь через 100 миллисекунд. По этой причине 100 миллисекунд - это не гарантированное время, а минимальное время. Следовательно, ваш doSomething methodвызов вызывается только в том случае, если стек пуст и в очереди больше ничего нет.

Но поскольку вы выполняете итерацию в цикле while, и ваше состояние зависит от кода внутри вашего setTimeout, вы создали бесконечный цикл, потому что стек не опустеет и, следовательно, ваш this.posts.push(this.postService.next(10));код никогда не будет вызываться.

Для реализаций RxJS то же самое верно. Они используют планировщики для обработки времени. Существуют разные реализации внутреннего планировщика в RxJS, но, как мы видим из реализаций для intervalи timer, если мы не указываем планировщик, по умолчанию используется asyncScheduler. AsyncScheduler планирует работу, setIntervalкоторая работает как setTimeoutуказано выше, и помещает другое сообщение в очередь.

Я попробовал два ваших решения с циклом while, и фактически первое полностью заморозило мой браузер, в то время как второе было очень медленным, но могло выводить что-то на консоль внутри цикла while. Я на самом деле не знаю, почему второй немного более производительный, но, тем не менее, оба не то, что вы на самом деле хотите. Вы уже нашли хорошее решение, и я надеюсь, что этот ответ поможет вам понять, почему первые решения работают так плохо.

Макс К
источник