在 Angular 中,如果有一个 Ajax call,默认从 promise 改成了 observable。我们来看一下:
// Promise
this.httpClient.get('/users/xxxx').toPromise().then(user => this.user = user);
我们很容易类比出 observable 的方案:
// Promise
this.httpClient.get('/users/xxxx').subscribe(user => this.user = user);
因为我们可以将 promise 看成只 emit 一次 value 的 observable。实际上通过简单的比较发现,then跟 subscribe 完全不同,
this.httpClient.get('/users/xxxx').toPromise().then
更加接近:
this.httpClient.get('/users/xxxx').subscribe
所以说,promise 跟 observable 更大的一个区别是,promise 相当于自动 subscribe 和 unsubscribe 的 observable。
所以你会发现,当我们把 promise 替换成 observable以后,不得不思考的问题是,到底什么时候 unsubscribe。
一般来说,在前端框架中,我们使用 observable,都会在 component 销毁的时候,执行 unsubscribe,用来防止内存泄漏。比如 Angular 的 ngDestroy。不难想到一个最简单直白的方案:
this.subscriptions = [];
this.subscriptions.push(
this.httpClient.get('/users/xxxx').subscribe();
);
//.....
//.....
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
这样写的问题也是很明显的,每次 subscribe 外面都莫名包裹了一层,增加了代码的复杂度。当然也可以定义一个局部变量,这样也增加了一些重复代码。
那么,有没有十分优雅的方法呢?其实暂时还真没有,有一个折中方案就是,我们可以减少 push 时的包裹。github.com/wardbell/su…
通过 subsink,我们唯一的好处是可以减少一层代码包裹,是相对比较优雅和折中的方案。
export class SomeComponent implements OnDestroy {
private subs = new SubSink();
...
this.subs.sink = observable$.subscribe(...);
this.subs.sink = observable$.subscribe(...);
this.subs.sink = observable$.subscribe(...);
...
// Unsubscribe when the component dies
ngOnDestroy() {
this.subs.unsubscribe();
}
}
其实不仅每次 unsubscribe 费时费力,subscribe 也是一件恨麻烦的事情,利用 Angular 提供的 async pipe,就可以相对比较容易的省略 subscribe 和 unsubscribe 的问题。但是,当 component 中的流数据比较多的时候,async 会出现使用比较麻烦的情况,可以参见前两篇的内容。
async 本质上是在 component 销毁的时候自动帮助你执行 unsubscribe,从而简化操作流程和代码。
实际上,如果从问题的本质出发,如果我们能够有效的防止 component 销毁后,observer 还在继续执行的话,其实是可以不用 unsubscribe 的。 RxJS 中有一系列的 take 操作符,就是用来把 observable 限定的一定的范围以内。
this.destroy$ = new Subject();
this.httpClient.get('/users/xxxx').takeUntil(this.destroy$).subscribe();
//.....
//.....
ngOnDestroy() {
this.destroy$.next(true);
}
当然,这种写法的缺点也恨明显,就是 destroy$ 本质上是没法受框架控制的,用户可以随意在其他地方 emit value,造成不可知的问题。