جاوااسکریپت - چرا setTimeout() باعث کند شدن اپلیکیشن من میشود، اما Rxjs timer().subscribe(...) اینطور نیست؟


من یک کامپوننت دارم که برخی نظرات را با فاصلهٔ ۱۰۰ میلیثانیه بهصورت «لود تنبل» بارگذاری میکند.

وقتی از setTimeout استفاده میکنم، واقعاً کند است.

جزء

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

این باعث کند شدن برنامه من میشود (میانگین نرخ فریم ۱۴، زمان بیکار ۵۱۱۰۰ میلیثانیه):

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

این باعث میشود برنامه من روان باشد (میانگین فریم بر ثانیه ۳۵، زمان بیکاری ۴۰۸۰۰ میلیثانیه)

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

آیا توضیحی وجود دارد که چرا تایمر rxjs اینقدر بهتر عمل میکند؟

من یک تحلیل زمان اجرا با فایرفاکس انجام دادم. در مثال اول، نرخ فریم به ۱۴ فریم بر ثانیه کاهش مییابد. در مثال دیگر، ۳۵ فریم بر ثانیه.

حتی زمان بیکاری ۲۰٪ کمتر است.

این روش حتی روانتر است (میانگین فریم بر ثانیه ۴۵، زمان بیکاری ۱۳۵۰۰ میلیثانیه):

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

پاسخ شماره ۱

آخرین راهحل شما تنها راهحل درست است.

دو راه حل دیگر نباید همانطور که انتظار داشتید کار کنند. در واقع، این باید منجر به یک حلقه بینهایت شود.

این به دلیل نحوه کار کردن حلقه رویداد جاوااسکریپت است. تصویر زیر یک مدل از زمان اجرای جاوااسکریپت را نشان میدهد (این تصویر از اینجا گرفته شده است):

قسمتهای مرتبط برای ما stack و queue هستند. یک زمان اجرای جاوااسکریپت پیامها را در queue پردازش میکند. هر پیام با یک تابع مرتبط است که هنگام پردازش پیام فراخوانی میشود.

برای پشته، هر فراخوانی تابع یک فریم روی پشته ایجاد میکند که شامل آرگومانها و متغیرهای محلی تابع است. اگر یک تابع، تابع دیگری را فراخوانی کند، یک فریم جدید روی بالای پشته قرار میگیرد. وقتی یک تابع بازمیگردد، فریم بالایی از پشته خارج میشود.

حالا اگر پشته خالی باشد، زمان اجرای جاوااسکریپت پیام بعدی را در صف (قدیمیترین پیام) پردازش خواهد کرد.

اگر از setTimeout(() => doSomething(),100) استفاده کنید، تابع doSomething() بعد از ۱۰۰ میلیثانیه به صف اضافه میشود. این دلیل این است که ۱۰۰ میلیثانیه زمان تضمینشده نیست بلکه حداقل زمان است. بنابراین، متد doSomething شما تنها زمانی فراخوانی میشود که پشته خالی باشد و هیچ چیز دیگری در صف وجود نداشته باشد.

اما همانطور که در یک حلقه while در حال تکرار هستید و شرط شما به کدی که داخل setTimeout است وابسته است، شما یک حلقه بینهایت ایجاد کردهاید زیرا پشته خالی نخواهد شد و بنابراین کد this.posts.push(this.postService.next(10)); شما هرگز اجرا نخواهد شد.

برای پیادهسازیهای RxJS نیز همین موضوع صادق است. آنها از زمانبندها برای مدیریت زمانبندی استفاده میکنند. پیادهسازیهای داخلی مختلفی از زمانبند در RxJS وجود دارد، اما همانطور که در پیادهسازیهای interval و timer مشاهده میکنیم، اگر زمانبندیکنندهای مشخص نکنیم، پیشفرض asyncScheduler است. asyncScheduler کارها را با setInterval زمانبندی میکند که مانند setTimeout که در بالا ذکر شد عمل میکند و پیام دیگری را به صف اضافه میکند.

من دو راهحل شما را با حلقه while امتحان کردم و در واقع راهحل اول کاملاً مرورگر من را فریز کرد، در حالی که راهحل دوم خیلی کند بود اما میتوانست چیزی را در داخل حلقه while به کنسول خروجی دهد. من واقعاً نمیدانم چرا راهحل دوم کمی بهتر عمل میکند، اما با این حال هیچکدام آنچه شما واقعاً میخواهید نیستند. شما قبلاً به یک راهحل خوب رسیدهاید و امیدوارم این پاسخ به شما کمک کند تا بفهمید چرا راهحلهای اول اینقدر بد عمل میکنند.