Правильный дизайн для повторяющихся приуроченных действий - PullRequest
0 голосов
/ 05 сентября 2018

Контекст

  • Угловой: 6.1.0
  • NgRx: 6.0.1
  • RxJS: 6.0.0

Я создаю страницу, на которой вы можете настроить некоторые будильники в определенное время, и они будут повторяемыми (допустим, вы хотите, чтобы он звонил в 10:00, он будет звучать в 10:00 каждый день).

Функция должна:

  • Уметь устанавливать «звонок на X минут раньше» для каждого будильника, сохраняя статистику «уже сыграно» для будильника, менять цвет на дисплее, когда он зазвонил, но время еще не пришло.
  • Уметь знать, сколько времени до того, как прозвучит сигнал тревоги.
  • Переносить другие разностные данные для отображения (и веб-уведомления).

Задача

Я перепробовал много способов моделирования этого с использованием RxJS, и в итоге получился плохой дизайн, который всегда имел серьезные недостатки из-за моих потребностей. Вот что я попробовал и почему это не удалось:

  • Используйте подписки, основанные на объекте галочки, который в основном излучает каждую 1 секунду (потому что точность должна быть 1 с).

    Это плохо, потому что я храню много подписок, и всякий раз, когда я редактирую сигнал тревоги, я должен уничтожать их и воссоздавать их после последних изменений в базе данных (не говоря уже о том, что это БД в реальном времени, поэтому обновления могут поступать с сервера без сделано текущим клиентом).

  • Используйте действие NgRx, которое отправляется каждую секунду, чтобы вызвать эффект, который проверяет наличие сигналов тревоги.

    Опять же, довольно плохо, потому что здесь я теряю некоторые данные, так как не могу найти правильный способ хранения пользовательских данных в этом поведении, а также затрудняет мониторинг «оставшегося времени до тревоги».

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

Ответы [ 2 ]

0 голосов
/ 05 сентября 2018

Сначала я бы создал класс Alarm, который может планировать себя и содержать его состояние:

class Alarm {
  public lastRangTime = 0;
  private stopper = new Subject<void>();

  get hasRang() {
    return this.lastRangTime > 0;
  }

  get remaining() {
    return this.ringTime - Date.now();
  }

  // add more metadata

  constructor(public ringTime: number, public interval: number) {}

  start(): Observable<number> {
    return timer(this.ringTime - Date.now(), this.interval)
      .pipe(
        tap(() => {
          this.lastRangTime = this.ringTime;
          this.ringTime += this.interval;
        }),
        takeUntil(this.stopper)
      )
  }

  stop() {
    this.stopper.next();
  }
}

И какой-нибудь контейнер / сервис для хранения всех подписок:

class AlarmScheduler {
  private queue = new Subject<Alarm>();
  private subscription: Subscription = null;

  schedule(ringTime: number, interval: number = DEFAULT_INTERVAL) {
    const alarm = new Alarm(ringTime, interval);

    this.queue.next(alarm);

    return alarm;
  }

  initialize() {
    this.subscription = this.queue
      .pipe(mergeMap(alarm => alarm.start()))
      .subscribe();
  }

  destroy() {
    this.subscription.unsubscribe();
  }
}

Чем вы могли бы просто запланировать тревоги от AlarmScheduler. Они будут повторяться с заданным интервалом.

const scheduler = new AlarmScheduler();

scheduler.initialize();

const a1 = scheduler.schedule(Date.now() + 5000);
const a2 = scheduler.schedule(Date.now() + 10000);

Рабочий пример: https://stackblitz.com/edit/typescript-uft7up

Конечно, вам придется проработать некоторые детали, но что касается планирования сигналов тревоги, я надеюсь, что приведенного выше кода достаточно для начала работы.

0 голосов
/ 05 сентября 2018

Создайте очередь сообщений и запустите рабочий, чтобы проверить, выполняется ли задание.

import { BehaviorSubject, timer, from} from 'rxjs';
import { withLatestFrom, map, mergeMap,  filter} from 'rxjs/operators';


class Queue {
  private queue = new BehaviorSubject<Task[]>([]);

  add(...tasks: Task[]) {
    this.queue.next([...this.queue.getValue(), ...tasks]);
  }
  remove(task: Task) {
    this.queue.next(this.queue.getValue().filter(item => item.id !== task.id));
  }
  asObservable() {
    return this.queue.asObservable();
  }
}

interface Task {
  id: number;
  at?: Date;
  period?: Date;
}

function isTimeForTask(task: Task){
  return true;
}

function runWorker(queue$){
  return  timer(0, 3000).pipe(
    withLatestFrom(queue$),
    map(([timer, queue])=>queue),
    mergeMap(queue=>from(queue)),
    filter(isTimeForTask)
  )
}

const queue = new Queue();

queue.add({id: 1});

runWorker(queue.asObservable())
  // Handle task
  .subscribe(console.log);

queue.add({id: 2});

setTimeout(()=>queue.add({id: 3}), 6000)
setTimeout(()=>queue.remove({id: 2}), 6000)
...