Приостановить интервал rxjs

Nov 04 2020

У меня есть простой секундомер с использованием rxjs

Проблема: не могу понять, как приостановить поток моего интервала, а затем продолжить его stackbiz

Ответы

1 MrkSef Nov 04 2020 at 16:02

Я видел, как вопросы о секундомере задаются достаточно часто, и подумал, что было бы интересно создать собственный наблюдаемый StopWatch. RxJS способом было бы осуществить это путем включения в и из таймеров / интервалов.

Другой интересный способ реализовать это - использовать вместо этого setTimeout. setTimeout на самом деле должен требовать немного меньше памяти, поскольку мы не полагаемся на наблюдаемый аппарат для достижения наших целей по времени

Как это будет работать? Наш настраиваемый наблюдаемый объект создает поток, который выводит число на секундомер и управляется отдельным потоком (здесь называется control$). Таким образом, когда control$выдает «СТАРТ», секундомер запускается, когда он издает «СТОП», секундомер останавливается, а когда он выдает «СБРОС», секундомер устанавливает счетчик обратно на ноль. Когда control$ошибка или завершается, секундомер выдает ошибку или завершает.

Реализовано с помощью switchMap и Timer

function createStopwatch(control$: Observable<string>, interval = 1000): Observable<number>{
  return defer(() => {
    let toggle: boolean = false;
    let count: number = 0;

    const endTicker$ = new Subject(); const ticker = () => { return timer(0, interval).pipe( takeUntil(endTicker$),
        map(x => count++)
      )
    }
  
    return control$.pipe( tap({ next: _ => {/*Do nothing*/}, complete: () => { endTicker$.next();
          endTicker$.complete(); }, error: err => { endTicker$.next();
          endTicker$.complete();
        }
      }),
      filter(control => 
        control === "START" ||
        control === "STOP" ||
        control === "RESET"
      ),
      switchMap(control => {
        if(control === "START" && !toggle){
          toggle = true;
          return ticker();
        }else if(control === "STOP" && toggle){
          toggle = false;
          return EMPTY;
        }else if(control === "RESET"){
          count = 0;
          if(toggle){
            return ticker();
          }
        }
        return EMPTY;
      })
    );
  });
}

Реализовано с помощью setTimeout

function createStopwatch(control: Observable<string>, interval = 1000): Observable<number> {
  return new Observable(observer => {
    let count: number = 0;
    let tickerId: number = null;

    const clearTicker = () => {
      if(tickerId != null){
          clearTimeout(tickerId);
          tickerId = null;
        }
    }
    const setTicker = () => {
      const recursiveTicker = () => {
        tickerId = setTimeout(() => {
          observer.next(count++);
          recursiveTicker();
        }, interval);
      }
      clearTicker();
      observer.next(count++);
      recursiveTicker();
    }

    control.subscribe({
      next: input => {
        if(input === "START" && tickerId == null){
          setTicker();
        }else if(input === "STOP"){
          clearTicker();
        }else if(input === "RESET"){
          count = 0;
          if(tickerId != null){
            setTicker();
          }
        }
      },
      complete: () => {
        clearTicker();
        observer.complete();
      },
      error: err => {
        clearTicker();
        observer.error(err);
      }
    });
  
    return {unsubscribe: () => clearTicker()};
  });
}

Секундомер в использовании

Вот пример использования этой наблюдаемой. Я управляю потоком управления через тему, но его можно так же легко объединить / сопоставить событиями DOM или что-то подобное.

const control$ = new Subject<string>();
createStopwatch(control$, 250).subscribe(console.log); // We send a new action to our control stream every 1 second const actions = ["START", "STOP", "START", "RESET", "START"] zip(from(actions), interval(1000)).pipe( map((x,y) => x), finalize(() => { // After 5 seconds, unsubscribe via the control // If our control finishes in any way ( // completes, errors, or is unsubscribed), our // sopwatch reacts by doing the same. control$.complete();
  })
).subscribe(x => control$.next(x));

Секундомер в использовании # 2

Это управляет секундомером с помощью setTimeoutвместо interval.

const control$ = new Subject<string>();
createStopwatch(control$, 250).subscribe(console.log); // We send a new action to our control stream every 1 second const actions = ["START", "STOP", "START", "RESET", "START"] actions.forEach((val, index) => { setTimeout(() => { control$.next(val);
  },
  index * 1000);
})

// Unsubscribe via the control
setTimeout(() => {
  control$.complete();
}, actions.length * 1000);
1 JanRecker Nov 04 2020 at 12:36

Взгляните на мое решение в этом stackblitz

Компонент должен подписаться на StopWatch-Service. Мне не нравится идея дать сервису значение, а затем сервис неявно МЕНЯЕТ значение. Поэтому я работаю с явным способом получить обновленный StopWatch (как наблюдаемый).

Я работаю с «таймером» вместо «интервала», потому что «интервал» будет выдавать первое значение (0) через секунду, поэтому мой stopWath будет иметь задержку,

Есть небольшая хитрость. Есть частная переменная "timer $", BehaviorSubject. И как только счетчик запускается, я запускаю таймер и подписываюсь на него. В этой подписке каждый запуск таймера будет выдавать новое значение для BehaviorSubject.

Теперь я тоже храню подписку.
Если я хочу все остановить, я просто отписываюсь от "таймера". В результате часы останавливаются. Но поскольку он как бы отделен от объекта behaviorSubject timer$, последнее значение все равно сохраняется.

Или, иначе говоря:
BehaviorSubject живет вечно, потребитель никогда не отписывается (только если он делает это сам). И я как бы «прикрепляю» и «отключаю» к нему функцию таймера, когда счет должен начинаться или останавливаться.

Между прочим, это хорошая привычка всегда быть уверенным, что ваш компонент откажется от подписки на все запущенные наблюдаемые объекты, когда компонент будет уничтожен. Если этого не сделать, то эти все еще активные подписки могут привести к снижению производительности или еще более серьезным проблемам в приложении.

Надеюсь, это немного поможет

теплые пожелания