RxJS  的 subscribe 和 unsubscribe

2,089 阅读1分钟

在 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。实际上通过简单的比较发现,thensubscribe 完全不同,

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,造成不可知的问题。

takeUntil(this.destroy$) 本质上还需呀等待 Angular 层面对 life cycle 函数的封装,或者能够 customize component class,防止用户在逻辑层手动修改 destroy$.