import { Observable } from '../Observable'; import { SchedulerLike } from '../types'; import { async as asyncScheduler } from '../scheduler/async'; import { isScheduler } from '../util/isScheduler'; import { isValidDate } from '../util/isDate'; /** * Creates an observable that will wait for a specified time period, or exact date, before * emitting the number 0. * * Used to emit a notification after a delay. * * This observable is useful for creating delays in code, or racing against other values * for ad-hoc timeouts. * * The `delay` is specified by default in milliseconds, however providing a custom scheduler could * create a different behavior. * * ## Examples * * Wait 3 seconds and start another observable * * You might want to use `timer` to delay subscription to an * observable by a set amount of time. Here we use a timer with * {@link concatMapTo} or {@link concatMap} in order to wait * a few seconds and start a subscription to a source. * * ```ts * import { of, timer, concatMap } from 'rxjs'; * * // This could be any observable * const source = of(1, 2, 3); * * timer(3000) * .pipe(concatMap(() => source)) * .subscribe(console.log); * ``` * * Take all values until the start of the next minute * * Using a `Date` as the trigger for the first emission, you can * do things like wait until midnight to fire an event, or in this case, * wait until a new minute starts (chosen so the example wouldn't take * too long to run) in order to stop watching a stream. Leveraging * {@link takeUntil}. * * ```ts * import { interval, takeUntil, timer } from 'rxjs'; * * // Build a Date object that marks the * // next minute. * const currentDate = new Date(); * const startOfNextMinute = new Date( * currentDate.getFullYear(), * currentDate.getMonth(), * currentDate.getDate(), * currentDate.getHours(), * currentDate.getMinutes() + 1 * ); * * // This could be any observable stream * const source = interval(1000); * * const result = source.pipe( * takeUntil(timer(startOfNextMinute)) * ); * * result.subscribe(console.log); * ``` * * ### Known Limitations * * - The {@link asyncScheduler} uses `setTimeout` which has limitations for how far in the future it can be scheduled. * * - If a `scheduler` is provided that returns a timestamp other than an epoch from `now()`, and * a `Date` object is passed to the `dueTime` argument, the calculation for when the first emission * should occur will be incorrect. In this case, it would be best to do your own calculations * ahead of time, and pass a `number` in as the `dueTime`. * * @param due If a `number`, the amount of time in milliseconds to wait before emitting. * If a `Date`, the exact time at which to emit. * @param scheduler The scheduler to use to schedule the delay. Defaults to {@link asyncScheduler}. */ export function timer(due: number | Date, scheduler?: SchedulerLike): Observable<0>; /** * Creates an observable that starts an interval after a specified delay, emitting incrementing numbers -- starting at `0` -- * on each interval after words. * * The `delay` and `intervalDuration` are specified by default in milliseconds, however providing a custom scheduler could * create a different behavior. * * ## Example * * ### Start an interval that starts right away * * Since {@link interval} waits for the passed delay before starting, * sometimes that's not ideal. You may want to start an interval immediately. * `timer` works well for this. Here we have both side-by-side so you can * see them in comparison. * * Note that this observable will never complete. * * ```ts * import { timer, interval } from 'rxjs'; * * timer(0, 1000).subscribe(n => console.log('timer', n)); * interval(1000).subscribe(n => console.log('interval', n)); * ``` * * ### Known Limitations * * - The {@link asyncScheduler} uses `setTimeout` which has limitations for how far in the future it can be scheduled. * * - If a `scheduler` is provided that returns a timestamp other than an epoch from `now()`, and * a `Date` object is passed to the `dueTime` argument, the calculation for when the first emission * should occur will be incorrect. In this case, it would be best to do your own calculations * ahead of time, and pass a `number` in as the `startDue`. * @param startDue If a `number`, is the time to wait before starting the interval. * If a `Date`, is the exact time at which to start the interval. * @param intervalDuration The delay between each value emitted in the interval. Passing a * negative number here will result in immediate completion after the first value is emitted, as though * no `intervalDuration` was passed at all. * @param scheduler The scheduler to use to schedule the delay. Defaults to {@link asyncScheduler}. */ export function timer(startDue: number | Date, intervalDuration: number, scheduler?: SchedulerLike): Observable; /** * @deprecated The signature allowing `undefined` to be passed for `intervalDuration` will be removed in v8. Use the `timer(dueTime, scheduler?)` signature instead. */ export function timer(dueTime: number | Date, unused: undefined, scheduler?: SchedulerLike): Observable<0>; export function timer( dueTime: number | Date = 0, intervalOrScheduler?: number | SchedulerLike, scheduler: SchedulerLike = asyncScheduler ): Observable { // Since negative intervalDuration is treated as though no // interval was specified at all, we start with a negative number. let intervalDuration = -1; if (intervalOrScheduler != null) { // If we have a second argument, and it's a scheduler, // override the scheduler we had defaulted. Otherwise, // it must be an interval. if (isScheduler(intervalOrScheduler)) { scheduler = intervalOrScheduler; } else { // Note that this *could* be negative, in which case // it's like not passing an intervalDuration at all. intervalDuration = intervalOrScheduler; } } return new Observable((subscriber) => { // If a valid date is passed, calculate how long to wait before // executing the first value... otherwise, if it's a number just schedule // that many milliseconds (or scheduler-specified unit size) in the future. let due = isValidDate(dueTime) ? +dueTime - scheduler!.now() : dueTime; if (due < 0) { // Ensure we don't schedule in the future. due = 0; } // The incrementing value we emit. let n = 0; // Start the timer. return scheduler.schedule(function () { if (!subscriber.closed) { // Emit the next value and increment. subscriber.next(n++); if (0 <= intervalDuration) { // If we have a interval after the initial timer, // reschedule with the period. this.schedule(undefined, intervalDuration); } else { // We didn't have an interval. So just complete. subscriber.complete(); } } }, due); }); }