Angular 中用 Subject 来做异步逻辑

举一个例子,在 electron 的 renderer 中,使用 ipcRenderer 来做通信,为了效率起见,一般使用异步的 send 方法.

这种情况下,如果是应答式的通信,main 进程发回的通信是不方便在同一个逻辑中完成的(当然也可以在这里listen channel,但是实现起来很奇怪,并且逻辑也不清晰),因此一般都是统一管理。

如果使用 Subject 来做异步处理,会非常方便。

用如下 service 来做样例。

import { Injectable, NgZone } from '@angular/core';
import { Observable, Subject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TaskService {
  // 这个 map 用来保存所有 Subject
  tasks = new Map<string, Subject<any>>();

  constructor(private zone: NgZone) {
    if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
      const {ipcRenderer} = window.require('electron');
      ipcRenderer.on('demo', (event, data) => {
        // 收到服务端返回之后,找对应的 subject,传入返回值,subscriber 就可以继续进行
        if (data.uuid && this.tasks.has(data.uuid)) {
          const subject = this.tasks.get(data.uuid);
          // 由于异步方法不在 zone 中,假如 subscriber 中有对 view 的操作,将无法正常更新,这里简单通过 zone.run 确保 subscriber 的正确执行
          this.zone.run(() => {
            subject.next(data);
          });
          // complete 解除所有 subscriber
          subject.complete();
          this.tasks.delete(data.uuid);
        }
      });
    }
  }

  send(param: string): Observable<any> {
    if (navigator.userAgent.toLowerCase().indexOf(' electron/') > -1) {
      const {ipcRenderer} = window.require('electron');
      // renderer 和 main 之间的通信,通过 uuid 来做通信唯一 ID,main 回复的时候会返回这个 uuid
      const uuid = uuidv4();
      const fullParam = {
        uuid,
        param,
      };
      ipcRenderer.send('demo', fullParam);
      // 发送之后返回 Subject,这样调用的地方就可以直接 subscribe
      const subject = new Subject();
      this.tasks.set(uuid, subject);
      return subject.asObservable();
    }
  }
}