rxjs
是一个强大的库,为我们提供了丰富的功能来处理异步数据流。在这些功能中,ReplaySubject
是一个非常有用的类,它在多种情况下表现突出。
ReplaySubject
是 Subject
的一种变体。与 Subject
类似,它是一个多播的 Observable,允许多个 Observer 订阅。然而,它有一个显著的不同点:它会缓存一定数量的值,并将这些值重新发射给所有新的订阅者。我们可以设定缓存的最大值数量或缓存的时间窗口。通过这种方式,ReplaySubject
使得新加入的订阅者能够接收到 Observable 中先前发射的值,即使这些值是在它们订阅之前发射的。
接下来通过详细分析其源代码来理解其工作原理。
在 rxjs
的源代码中,ReplaySubject
实际上是继承自 Subject
的:
class ReplaySubject<T> extends Subject<T> {
private buffer: T[] = [];
private bufferSize: number;
private windowTime: number;
private scheduler: SchedulerLike;
constructor(bufferSize: number = Number.POSITIVE_INFINITY, windowTime: number = Number.POSITIVE_INFINITY, scheduler?: SchedulerLike) {
super();
this.bufferSize = bufferSize < 1 ? 1 : bufferSize;
this.windowTime = windowTime < 1 ? 1 : windowTime;
this.scheduler = scheduler;
}
}
ReplaySubject 的构造函数接受三个参数:
bufferSize
:缓存的最大值数量,默认值为Number.POSITIVE_INFINITY
。windowTime
:缓存的时间窗口,默认值为Number.POSITIVE_INFINITY
,表示缓存的时间跨度是无限的。scheduler
:可选的调度器,用于时间管理。
关键方法之一是 next
方法,它是用于发射一个值:
next(value?: T): void {
const now = this.scheduler ? this.scheduler.now() : Date.now();
this.buffer.push({ value, timestamp: now });
this.trimBufferThenGetEvents();
super.next(value);
}
这个方法除了调用 super.next(value)
以外,它还将值以及时间戳缓存到 buffer
中,并调用 trimBufferThenGetEvents
方法来维护缓存。
trimBufferThenGetEvents
方法是核心部分,它用来修剪缓存,确保它遵循 bufferSize
和 windowTime
的限制:
private trimBufferThenGetEvents() {
const now = this.scheduler ? this.scheduler.now() : Date.now();
const bufferSize = this.bufferSize;
const windowTime = this.windowTime;
const buffer = this.buffer;
let events = buffer.slice();
if (bufferSize < Number.POSITIVE_INFINITY) {
events = events.slice(-(bufferSize));
}
if (windowTime < Number.POSITIVE_INFINITY) {
events = events.filter(event => (now - event.timestamp) < windowTime);
}
this.buffer = events;
}
每次有新的值发射时,这个方法会对缓存进行修剪,确保其不超过设定的 bufferSize
和 windowTime
。
当订阅者订阅 ReplaySubject
时,他们会收到已经缓存的事件:
_subscribe(subscriber: Subscriber<T>): Subscription {
const events = this.trimBufferThenGetEvents();
for (let i = 0; i < events.length && !subscriber.closed; i++) {
subscriber.next(events[i].value);
}
return super._subscribe(subscriber);
}
这个方法首先调用 trimBufferThenGetEvents
修剪缓冲区,然后将缓冲区中的所有事件发送给新订阅者。
下面是一个示例,展示 ReplaySubject
的基本用法:
import { ReplaySubject } from 'rxjs';
// 创建一个 ReplaySubject,缓存最近 2 个值
const replaySubject = new ReplaySubject(2);
// 订阅该 ReplaySubject
replaySubject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
// 发射 3 个值
replaySubject.next(1);
replaySubject.next(2);
replaySubject.next(3);
// 这里 observerB 会接收到最近的 2 个值 2 和 3
replaySubject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
// 依然会接收到值
replaySubject.next(4);
在这个例子中,ReplaySubject
缓存最近 2 个值。当 observerB
订阅时,它会接收到值 2
和 3
。发射值 4
时,两个订阅者都接收到了。
ReplaySubject 的使用场景
- 缓存数据:当您希望新的订阅者能够立即接收到最新的数据(可能是在它们订阅之前发射的),可以使用
ReplaySubject
。例如在实时聊天应用中,当新用户加入会话时,您可能希望他们看到最近的几条消息。 - 重复播放:在某些情况下,您希望重播某个数据序列。例如,音视频播放器的实现中,如果需要实现某种形式的回放功能,可以考虑采用
ReplaySubject
来缓存数据流并再现。 - 缓冲最新的信息:当某些数据流速特别快且订阅者可能会错过某些信息时,可以使用
ReplaySubject
缓存这些信息,使得订阅者不会错过重要的信息。 - 保存最近状态:在 Redux 或其他状态管理工具中,您可能想要保存最近的状态,以便在用户重新打开页面时能够恢复状态,可以使用
ReplaySubject
来实现保存和恢复状态的功能。
下一步,通过一个更复杂的例子来展示 ReplaySubject
的更多功能,这里演示一个场景,即显示最近推送的三条数据,并每隔一段时间推送新数据:
import { ReplaySubject } from 'rxjs';
import { interval } from 'rxjs';
import { take } from 'rxjs/operators';
// 创建一个 ReplaySubject,缓存最近 3 个值
const replaySubject = new ReplaySubject(3);
// 订阅该 ReplaySubject
replaySubject.subscribe({
next: (v) => console.log(`observerA: ${v}`)
});
// 使用 interval 生成一个数字流,每隔 1 秒发射一个值,最多发射 5 个值
interval(1000).pipe(take(5)).subscribe(value => replaySubject.next(value));
// 延时 3 秒后再加入新订阅者
setTimeout(() => {
replaySubject.subscribe({
next: (v) => console.log(`observerB: ${v}`)
});
}, 3000);
在这个例子中,observerA
会从 0
到 4
收到所有值。observerB
延时 3 秒之后订阅,由于 ReplaySubject
记住了最近的 3 个值,observerB
会收到值 2
, 3
, 4
。
综上所述,ReplaySubject
是 rxjs
中非常有用的工具,为我们提供了一种缓存和重播数据流的方式。了解它的工作原理和使用场景,及其源代码的分析,能让我们在实际项目中更好地利用它来解决复杂的异步处理问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。