switchMap 操作符通过名字便知和 mergeMap 很像,是用来处理嵌套流的。而它们之间的不同之处在于,switchMap 会先查看是否有“内部”订阅,如果有,switchMap 会先取消这个“内部”订阅,再对接收到的值做处理。
文字描述可能还不让你理解,我们再用代码来演示下它们的不同。
fromEvent(document, "click").pipe(
scan(i => i + 1, 0),
switchMap(value => of(value).pipe(delay(500))) // 在这行代码分别使用 mergeMap 和 switchMap
)
.subscribe(value => {
console.log(value);
});
首先使用 mergeMap,在页面上连续点击 5 次,控制台将输出 1 2 3 4 5。也就是说每次点击产生的值都会被 mergeMap 处理。再来看看 switchMap,在页面上连续点击 5 次后,我们发现控制台只输出了一次,值为 5。这就是我们在文章开头说的,switchMap 每接收到新值时会检查之前有没有发生过订阅,如果有,先取消,再处理新值。由于示例代码中有 500 毫秒的延迟,只要比我们点击页面的速度慢,导致 2 3 4 5 次点击都会先取消上一次点击事件流下来的事件,再处理本次事件,由于第五次点击后面没有事件了,所以输出了 5。这就是 switchMap 的工作机理。
了解了 switchMap 的工作方式,我们就可以来写代码实现它了。首先拷贝之前写好的 MyMergeMap 代码:
class MySwitchMapSubscriber extends Subscriber {
constructor(sub, fn) {
super(sub);
this.fn = fn;
}
_next(value) {
const innerObservable$ = this.fn(value);
innerObservable$.subscribe(value => {
this.destination.next(value);
});
}
}
const mySwitchMap = fn => source => {
return source.lift({
call(sub, source) {
source.subscribe(new MySwitchMapSubscriber(sub, fn));
}
});
};
下面我们来改造代码。根据我们了解到的 switchMap 的工作机理,首先我们要有内部订阅对象,这是由 innerObservable$.subscribe 这段代码产生的。然后我们在这段代码执行前对这个内部订阅对象进行检查,如果有值,说明有过订阅,就 unsubscribe 它。修改后代码如下:
class MySwitchMapSubscriber extends Subscriber {
constructor(sub, fn) {
super(sub);
this.fn = fn;
this.innerSubscription = null;
}
_next(value) {
const innerObservable$ = this.fn(value);
if (this.innerSubscription) {
this.innerSubscription.unsubscribe();
}
this.innerSubscription = innerObservable$.subscribe(value => {
this.destination.next(value);
});
}
}
const mySwitchMap = fn => source => {
return source.lift({
call(sub, source) {
source.subscribe(new MySwitchMapSubscriber(sub, fn));
}
});
};
我们在 MySwitchMapSubscriber 自定义类的构造函数中,创建了成员变量 innerSubscription,用来保存内部订阅对象的值。然后我们在订阅内部对象前,加入了代码检查这个成员变量是否有值,有值就 unsubscribe 它。这样自制版的 switchMap 操作符就完成了。是不是很简单?它和 mergeMap 操作符的区别可以说只有一行代码之差。假设示例代码的每次点击都和一个 http 请求关联,这种场景该用 mergeMap 还是 switchMap 呢?留给大家去思考吧。
如有任何问题,请添加微信公众号“读一读我”。