我有一个RxJS Observable,需要在特定时间重新计算,如DateTime
对象数组所描述(尽管出于这个问题的目的,它们可能是JavaScript Date
对象,纪元毫秒或代表特定时间点的其他任何东西):
const changeTimes = [
// yyyy, mm, dd, hh, mm
DateTime.utc( 2018, 10, 31, 21, 45 ),
DateTime.utc( 2018, 10, 31, 21, 50 ),
DateTime.utc( 2018, 10, 31, 22, 00 ),
DateTime.utc( 2018, 10, 31, 23, 00 ),
DateTime.utc( 2018, 10, 31, 23, 30 ),
];
我正在努力了解如何创建一个在这样的数组中指定的时间发射的Observable。
这就是我想回答我自己的问题的想法:
delay
operator,其中指定的延迟是“现在”到下一个未来日期时间之间的时间。defer
operator),尽管我不想不必要地创建多个Observable实例,如果有多个订阅。expand
operator可能是我所需要的,但是它递归地调用了某些东西,而我只是想遍历列表。timer
operator似乎无关紧要,因为每个日期时间之间的持续时间是不同的。merge
将它们全部返回,但是随着数组中日期时间数量的增加(可能有数百个),这变得非常低效。不得已。如何使RxJS Observable接收日期时间列表,然后在到达每个时间时发出,并在最后一个时间完成?
答案 0 :(得分:1)
我认为您在要点中概述的内容都是正确的。使用delay
似乎很明显,但会使链条难以理解。
我想到的解决方案是假设您在创建可观察链之前就知道changeTimes
数组。您可以创建自己的“可观察的创建方法”,该方法将返回一个基于setTimeout
发出的Observable(例如,这只是“伪代码”,它无法正确计算日期):
const schedule = (dates: Date[]): Observable<Date> => new Observable(observer => {
// Sort the `dates` array from the earliest to the latest...
let index = 0;
let clearTimeout;
const loop = () => {
const now = new Date();
const delay = dates[index] - now;
clearTimeout = setTimeout(() => {
observer.next(dates[index++]);
if (index < dates.length) {
loop();
}
}, delay);
}
loop();
return () => clearTimeout(clearTimeout);
});
...
schedule(changeTimes)
.subscribe(...)
您在merge
上提到的最后一个选项实际上还不错。我了解您担心它会创建很多订阅,但是如果您对changeTimes
数组进行排序,然后使用concat
而不是merge
,它将始终仅保留一个活动订阅即使您创建了100个Observable。
答案 1 :(得分:0)
这是一个可行的示例:
import {Injectable} from '@angular/core';
import {Observable, Subject, timer} from 'rxjs';
@Injectable()
export class TimerService {
futureDates: Date[] = [];
futureDate: Date;
notifier: Observable<string>;
cycle = (observer) => {
if (this.futureDates.length > 0) {
this.futureDate = this.futureDates.shift();
const msInFuture = this.futureDate.getTime() - Date.now();
if (msInFuture < 0) {
console.log(`date ${this.futureDate.toISOString()}
expected to be in the future, but was ${msInFuture} msec in the past, so stopping`);
observer.complete();
} else {
timer(msInFuture).subscribe(x => {
observer.next(`triggered at ${new Date().toISOString()}`);
this.cycle(observer);
});
}
} else {
observer. complete();
}
}
getTimer(): Observable<string> {
const now = new Date();
const ms1 = now.getTime() + 10000;
const ms2 = now.getTime() + 20000;
this.futureDates.push(new Date(ms1));
this.futureDates.push(new Date(ms2));
this.notifier = new Observable(observer => {
this.cycle(observer);
});
return this.notifier;
}
}
在此示例中,未来时间列表是在getTimer()
方法中创建的,但是您可以将一系列日期传递给该方法。
关键是简单地存储日期,一次处理一个,然后在处理时间,检查该日期在将来的时间,并为该毫秒数设置一个一次性Rx计时器。
答案 2 :(得分:0)
给出DateTime
个对象的数组:
const changeTimes = [
// yyyy, mm, dd, hh, mm
DateTime.utc( 2018, 10, 31, 21, 45 ),
DateTime.utc( 2018, 10, 31, 21, 50 ),
DateTime.utc( 2018, 10, 31, 22, 00 ),
DateTime.utc( 2018, 10, 31, 23, 00 ),
DateTime.utc( 2018, 10, 31, 23, 30 ),
];
或者更好的是,一个Observable每次数组更改时都会发出一个数组(这是我的情况下实际发生的情况,尽管我没有在问题中提及它,因为它并不严格相关):
const changeTimes$: Observable<DateTime[]> = /* ... */;
以下Observable将立即发出订阅的下一个将来时间,在上一个将来时间过去时发出每个以后的将来时间,然后完成null
:
const nextTime$ = changeTimes$.pipe(
// sort DateTimes chronologically
map(unsorted => [...unsorted].sort((x, y) => +x - +y),
// remove duplicates
map(duplicated => duplicated.filter((item, i) => !i || +item !== +duplicated[i - 1])),
// convert each time to a delayed Observable
map(times => [...times, null].map((time, i) => defer(() => of(time).pipe(
// emit the first one immediately
// emit all others at the previously emitted time
delay(i === 0 ? 0 : +times[i - 1] - +DateTime.utc())
)))),
// combine into a single Observable
switchMap(observables => concat(...observables)),
);
defer
用于在订阅内部Observable时计算当前时间。concat
用于连续执行每个内部Observable(由于martin),从而避免了同时在列表中每次预订的开销。这满足了我最初的需求:
我有一个RxJS Observable,它需要在特定的时间重新计算,如DateTime对象数组所描述的那样。
是如果我将其与需要使用the combineLatest
operator进行重新计算的数据结合以在正确的时间触发该重新计算:
const timeAwareData$ = combineLatest(timeUnawareData$, nextTime$).pipe(
tap(() => console.log('either the data has changed or a time has been reached')),
// ...
);
它同时为列表中的每个时间创建一个单独的内部Observable。我觉得有可能重构,以便每个内部Observable仅在销毁前一个内部Observable之后创建。任何改进技巧将不胜感激。
答案 3 :(得分:-1)