ایجاد تایمرها در برنامه‌های Angular
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هفت دقیقه

عموما در برنامه‌های جاوا اسکریپتی با استفاده از متدهای setTimeout و setInterval می‌توان یک تایمر را ایجاد کرد. اما در برنامه‌های Angular با توجه به استفاده‌ی از کتابخانه‌ی RxJS، امکان ایجاد تایمرهای reactive نیز وجود دارد که در این مطلب آن‌ها را مرور خواهیم کرد.


ایجاد تایمرهای متوالی و بی‌وقفه

با استفاده از عملگر Observable.interval می‌توان یک تایمر بی‌نهایت را ایجاد کرد. پارامتر ورودی آن بر حسب میلی ثانیه است و مشترکین به آن در بازه‌های زمانی مشخص شده‌ی توسط این پارامتر، عدد جاری این بازه را دریافت می‌کنند.
یک مثال:


در این مثال می‌خواهیم تایمری را ایجاد کنیم که هر ثانیه یکبار، کدی را اجرا کند:
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/interval";
import { Subscription } from "rxjs/Subscription";

@Component()
export class UsingTimersComponent {

  private intervalSubscription: Subscription;
  interval = 0;

  startInterval() {
    const interval = Observable.interval(1000);
    this.intervalSubscription = interval.subscribe(i => this.interval += i);
  }

  stopInterval() {
    this.intervalSubscription.unsubscribe();
  }
}
با این قالب:
<div class="panel panel-default">
  <div class="panel-heading">
    <h2 class="panel-title">Observable.interval(1000)</h2>
  </div>
  <div class="panel-body">
    <div>
      <label>interval: </label> {{interval}}
    </div>
    <div>
      <button (click)="startInterval()" class="btn btn-success">Start</button>
      <button (click)="stopInterval()" class="btn btn-danger">Stop</button>
    </div>
  </div>
</div>
عملگر interval باید از مسیر rxjs/add/observable/interval دریافت شود که در ابتدای تعاریف کامپوننت مشخص شده‌است.
 پس از آن فراخوانی Observable.interval(1000) یک Observable را ایجاد می‌کند که توانایی صدور رخ‌دادهایی را در بازه‌های زمانی متوالی 1000 میلی ثانیه‌ای، دارا است.
اکنون مشترکین به آن، اعداد متوالی شروع شده‌ی از صفر را در هر ثانیه یکبار، دریافت می‌کنند:
this.intervalSubscription = interval.subscribe(i => this.interval += i);
این تایمر، به نحوی که تعریف شده‌است، تا ابد ادامه پیدا خواهد کرد. برای توقف آن نیاز است همانند روال معمول کار با Observableها، اشتراک به آن را لغو کرد:
this.intervalSubscription.unsubscribe();


مطلع شدن از پایان کار یک تایمر

با استفاده از اپراتور finally که از مسیر rxjs/add/operator/finally قابل import است، می‌توان رخ‌داد لغو اشتراک به این Observable و یا همان خاتمه‌ی تایمر را در اینجا دریافت کرد:
this.intervalSubscription = interval
      .finally(() => console.log("All done!"))
      .subscribe(i => this.interval += i);


ایجاد تایمرهای خود متوقف شونده

با استفاده از عملگر Observable.timer که در مسیر rxjs/add/observable/timer قرار دارد، می‌توان تایمری را ایجاد کرد که پس از یک تاخیر مشخص شده‌، اجرا شود و بلافاصله خاتمه یابد:
const timer = Observable.timer(1000);
timer.subscribe(data => console.log('ding!'));
در اینجا تایمری ایجاد شده‌است که پس از یک ثانیه اجرا شده و کد نمایش ding را در کنسول مرورگر اجرا می‌کند. سپس به صورت خودکار خاتمه خواهد یافت. در اینجا data نیز مساوی صفر است (اولین بار اجرای تایمر).
این تایمر امکان اجرای در بازه‌های زمانی مشخصی را نیز دارا است:
const moreThanOne$ = Observable.timer(2000, 500);
moreThanOne$.subscribe(data => console.log('timer with args', data));
اولین پارامتر آن مشخص می‌کند که این تایمر باید پس از 2 ثانیه تاخیر، شروع به کار کند و دومین آرگومان آن مشخص می‌کند که این تایمر تا ابد، با فواصل زمانی هر 500 میلی‌ثانیه یکبار، اجرا خواهد شد.


محدود کردن تعداد بار اجرای تایمر

اگر Observable.timer با پارامتر دوم آن بکار رود، بی‌نهایت بار اجرا خواهد شد. اما می‌توان این تعداد بار اجرا را توسط اپراتور take که از مسیر rxjs/add/operator/take قابل import است، محدود کرد:
let moreThanOne$ = Observable.timer(2000, 500).take(3);
moreThanOne$.subscribe(data => console.log('timer with args', data));
در اینجا تایمر تعریف شده، پس از یک وقفه‌ی آغازین 2 ثانیه‌ای شروع به کار می‌کند. سپس تنها دو بار دیگر در بازه‌های متوالی زمانی 500 میلی ثانیه یکبار، اجرا خواهد شد. یعنی جمعا سه بار با توجه به take(3) اجرا خواهد شد.


اجرای با تاخیر بازه‌های زمانی

با استفاده از اپراتور delay که از مسیر rxjs/add/operator/delay قابل import است، می‌توان هر بار اجرای callback تایمر را با یک تاخیر دریافت کرد:
const start = new Date();
const stream$ = Observable.interval(500).take(3);
stream$.delay(300).subscribe(x => {
    console.log('val',x);
    console.log( new Date() - start );
})
در اینجا تایمر از نوع interval تعریف شده، با توجه به استفاده‌ی از عملگر take، تنها سه بار اجرا می‌شود. اما این اجراها با تاخیری 300 میلی‌ثانیه‌ای به مشترکین آن‌ها اطلاع رسانی می‌گردند. به این ترتیب خروجی لاگ شده‌ی این عملیات به صورت ذیل خواهد بود:
val:0
800ms
val:1
1300ms
val:2
1800ms


ایجاد یک تایمر شمارش معکوس

فرض کنید می‌خواهید تایمری را ایجاد کنید که در طی یک شمارش معکوس، از عدد 10000 شروع شود و هر ثانیه یکبار 1000 واحد از آن کاهش یابد و زمانیکه به صفر رسید، متوقف شود.
این تایمر پس از import وابستگی‌های آن:
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/timer";
import "rxjs/add/operator/finally";
import "rxjs/add/operator/takeUntil";
import "rxjs/add/operator/map";
یک چنین تعریفی را پیدا می‌کند:
const interval = 1000;
const duration = 10 * 1000;
const stream$ = Observable.timer(0, interval)
      .finally(() => console.log("All done!"))
      .takeUntil(Observable.timer(duration + interval))
      .map(value => duration - value * interval);
stream$.subscribe(value => console.log(value));
در اینجا تایمر تعریف شده با توجه به آرگومان صفر تاخیر آن، بلافاصله شروع به کار می‌کند. همچنین با توجه به عدد interval آن، هر یک ثانیه یکبار اعداد صفر، یک و ... را به مشترکین خود ارسال خواهد کرد. اکنون می‌خواهیم این تایمر دقیقا پس از 11 ثانیه متوقف شود. یکی از روش‌های پیاده سازی آن استفاده از takeUntil است که در اینجا یک تایمر خود متوقف شوند را دریافت کرده‌است. این تایمر دقیقا پس از 11 ثانیه از شروع عملیات، یکبار اجرا شده و بلافاصله خاتمه پیدا می‌کند. همین صدور رخ‌داد، کار takeUntil را به پایان می‌رساند که این مورد نیز سبب خاتمه‌ی تایمر اصلی می‌شود.
در اینجا چون اعداد صادر شده‌ی از طرف تایمر، افزایشی هستند، نیاز است به روشی آن‌ها را تغییر داد. در یک چنین حالتی از اپراتور map استفاده می‌شود. در اینجا value، هربار مقدار افزایشی شروع شده‌ی از صفر را ارائه می‌دهد. توسط عملگر map، این خروجی افزایشی را به یک خروجی کاهشی تبدیل کرده‌ایم تا بتوان به یک تایمر شمارش معکوس رسید.


دریافت مدت زمان بین اجرای بازه‌های زمانی

Observable.timer با هر بار اجرا، اعداد شروع شده‌ی از صفر را به مشترکین ارسال می‌کند. اگر در این بین از اپراتور timeInterval قرار گرفته‌ی در مسیر rxjs/add/operator/timeInterval استفاده شود، این مقدار ارسالی از نوع مخصوص <TimeInterval<number خواهد بود که دارای خواص value و interval است:
const source = Observable.timer(0, 1000)
      .timeInterval()
      .map(x => x.value + ":" + x.interval)
      .take(5);

const subscription = source.subscribe(
      x => console.log("Next timeInterval: " + x),
      err => console.log("Error: " + err),
      () => console.log("Completed")
    );
در اینجا value همان صفر، یک و ... است و interval بیانگر زمان سپری شده‌ی بین دو صدور رخ‌داد می‌باشد.
در این مثال با استفاده از متد map، یک خروجی سفارشی تهیه شده‌است. اگر صرفا علاقمند به دریافت مقدار خاصیت interval باشید، می‌توان به صورت ذیل نیز عمل کرد:
const source = Observable.timer(0, 1000)
      .timeInterval()
      .pluck("interval")
      .take(5);
عملگر pluck که در مسیر rxjs/add/operator/pluck قرار دارد، خاصیت و یا خاصیت‌هایی از منبع را جهت بازگشت، انتخاب می‌کند. برای مثال در اینجا خاصیت interval یک شیء TimeInterval انتخاب شده‌است.


تعلیق و از سرگیری مجدد تایمرها

با قطع اشتراک از یک منبع تایمر، سبب توقف کامل آن خواهیم شد. اما اگر برای مدتی بخواهیم آن‌را در حالت تعلیق قرار دهیم، می‌توان به صورت ذیل عمل کرد:
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/never";
import "rxjs/add/observable/timer";
import { Subject } from "rxjs/Subject";

  tick: number;
  pauser = new Subject();
  tickerSource = new Subject();
  startTicker() {
    Observable.timer(0, 1000)
      .subscribe(this.tickerSource);

    this.pauser
      .switchMap(paused => paused ? Observable.never() : this.tickerSource).
      subscribe(t => this.tickerFunc(t));

    this.pauser.next(false); // resume
  }

  tickerFunc(tick) {
    this.tick = tick;
  }

  pauseTicker() {
    this.pauser.next(true);
  }

  resumeTicker() {
    this.pauser.next(false);
  }
نکته‌ی اصلی این طراحی در switchMap و Observable.never آن نهفته‌است. در اینجا وجود Subject سبب صدور رخدادی به مشترکین آن می‌شود. اگر توسط متد next آن false ارسال شود، سبب از سرگیری مجدد منبع اصلی یا همان تایمر برنامه می‌شود و اگر true ارسال شود، عملیات فراخوانی tickerFunc را با فراخوانی Observable.never به حالت تعلیق می‌برد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
  • #
    ‫۶ سال و ۷ ماه قبل، سه‌شنبه ۱۷ بهمن ۱۳۹۶، ساعت ۱۷:۵۱
    یک نکته‌ی تکمیلی: روش ایجاد تایمرها در «RxJS 5.5»