Почему setTimeout () делает мое приложение медленным, а Rx js timer (). Subscribe (...) - нет? - PullRequest
9 голосов
/ 04 марта 2020

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

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

component

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

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

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

Это делает мое приложение гладким (среднее значение fps 35, простой 40800 мс)

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

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

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

Даже время простоя на 20% ниже.

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

interval(100).pipe(takeWhile(this.postService.hasPosts()).subscribe(()=> {
    this.posts.push(this.postService.next(10));
  });
}

1 Ответ

2 голосов
/ 05 марта 2020

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

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

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

enter image description here

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

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

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

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

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

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

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

...