Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does setTimeout() make my app laggy, but Rxjs timer().subscribe(...) does not?

I have a component, that "lazy loads" some comments, at the interval of 100ms.

When I use setTimeout, it is really laggy.

component

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

This makes my Application laggy (avg fps 14, idle time 51100ms):

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

This make my Application smooth (avg fps 35, idle time 40800ms)

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

Is there any explaination, why the rxjs timer, works that much better ?

I did a runtime analysis with firefox. In the first example, the frame rate drops to 14 fps In the other example, 35 fps.

Even the idle time is 20% lower.

This method is even smoother (avg fps 45, idle time 13500ms):

interval(100).pipe(takeWhile(this.postService.hasPosts()).subscribe(()=> {
    this.posts.push(this.postService.next(10));
  });
}
like image 803
Luxusproblem Avatar asked Mar 04 '20 19:03

Luxusproblem


1 Answers

Your last solution is the only correct one.

The other two solutions should not work like you expected them to work. Actually, this should result in an infinite loop.

This is because of how JavaScript's eventloop works. The following picture shows a model of the JavaScript runtime (Image was taken from here):

enter image description here

The relevant parts for us are the stack and the queue. A JavaScript runtime processes the messages on the queue. Each message is associated with a function which gets called as the message is processed.

For the stack, each function call creates a frame on the stack which contains the functions arguments and local variables. If a function calls another function a new frame is pushed on top of the stack. When a function returns the top frame is popped out of the stack.

Now if the stack is empty the JavaScript runtime will process the next message on the queue (the oldest one).

If you use setTimeout(() => doSomething(),100), the doSomething() function gets added to the queue after 100 milliseconds. This is the reason why the 100 milliseconds is not a guaranteed time but a minimal time. Therefore your doSomething method only gets called, if the stack is empty and nothing else is in the queue.

But as you are iterating in a while loop and your condition depends on code inside your setTimeout, you've created an infinite loop because the stack will not get empty and therefore your this.posts.push(this.postService.next(10)); code will never be called.

For the RxJS implementations the same is true. They use schedulers to handle timing. There are different internal scheduler implementations in RxJS, but as we can see in the implementations for interval and timer, if we don't specify a scheduler the default one is the asyncScheduler. The asyncScheduler schedules work with setInterval which works like setTimeout mentioned above, and pushes another message on the queue.

I tried your two solutions with the while loop and actually the first one totally froze my browser while the second one was super laggy but could output something to the console inside of the while loop. I actually don't know why the second one is a little bit more performant, but nevertheless both are not what you actually want. You already came up with a good solution and I hope this answer may help you understand why the first solutions perform so bad.

like image 172
Max K Avatar answered Oct 18 '22 23:10

Max K