正确设计重复计时动作

时间:2018-09-05 08:28:03

标签: angular rxjs ngrx

上下文

  • 角度:6.1.0
  • NgRx:6.0.1
  • RxJS:6.0.0

我正在创建一个页面,您可以在该页面上设置给定时间的一些警报,并且这些警报是可重复的(假设您希望它在每天10:00 AM响起,每天10:00 AM响起)。

该功能必须:

  • 能够为每个警报设置“在X分钟之前响起”,保持警报的“已播放”状态,并在响起时更改显示颜色,但还不是时候。
  • 能够知道在给定警报响起之前需要多长时间。
  • 携带其他杂项数据以用于显示(和网络通知)。

问题

我尝试了许多使用RxJS对此进行建模的方法,但最终由于我的需要而导致糟糕的设计始终存在重大缺陷。这是我尝试过的原因以及失败的原因:

  • 使用基于一个滴答主题的订阅,该主题基本上每1秒发出一次(因为精度必须为1s)。

    这很糟糕,因为我存储了很多订阅,每当编辑警报时,我都必须销毁它们并从最新的数据库更改中重新创建它们(更不用说这是实时数据库,因此更新可以来自服务器而无需由当前客户制作)。

  • 使用NgRx动作,该动作每秒钟分派一次,以触发一种检查警报响起的效果。

    再次很糟糕,因为在这里我丢失了一些数据,因为找不到这种行为来存储自定义数据的正确方法,这也使“报警前的剩余时间”监控变得非常困难。

总的来说,问题是设计不好,我找不到实现这种行为的正确方法,主要是因为我想避免每秒调用一次方法,但是另一方面,我似乎找不到这样做的另一种方式,可能是因为我对此解决方案心不在mind,这就是为什么我总是在这里。

2 个答案:

答案 0 :(得分:2)

首先,我将创建一个可以调度自身并包含其状态的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

当然,您必须确定一些细节,但是就安排警报而言,我希望上面的代码足以帮助您入门。

答案 1 :(得分:1)

创建message queue并运行worker以检查要运行的任务。

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)