[譯] RxJS: multicast 操作符的祕密

SangKa發表於2018-02-01

原文連結: blog.angularindepth.com/rxjs-multic…

本文為 RxJS 中文社群 翻譯文章,如需轉載,請註明出處,謝謝合作!

如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點選【這裡】

multicast 操作符有一個祕密。publish 操作符也是如此,它封裝了 multicast 。這個祕密有時候真的挺好用的。

祕密

multicastpublish 的文件中都提到了 ConnectableObservableConnectableObservable 是一種特殊型別的 observable,只有呼叫它的 connect 方法後,它才會開始向訂閱者傳送通知。然而,multicastpublish 操作符並非永遠返回 ConnectableObservable

我們先來看下 publish原始碼:

export function publish<T>(
  this: Observable<T>,
  selector?: (source: Observable<T>) => Observable<T>
): Observable<T> | ConnectableObservable<T> {
  return selector ?
    multicast.call(this, () => new Subject<T>(), selector) :
    multicast.call(this, new Subject<T>());
}
複製程式碼

可以很清楚地看出,publish 只是對 multicast 進行了一層很薄的封裝。它建立了 subject 並傳給 multicast,還有一個可選的 selector 函式。最有趣的部分是在 multicast 實現之中,它包含如下程式碼:

if (typeof selector === 'function') {
  return this.lift(new MulticastOperator(subjectFactory, selector));
}

const connectable: any = Object.create(this, connectableObservableDescriptor);
connectable.source = this;
connectable.subjectFactory = subjectFactory;
return <ConnectableObservable<T>> connectable;
複製程式碼

只有在不傳入 selector 函式的情況下,multicast 才返回 ConnectableObservable。如果傳入 selector 函式的話,會使用 lift 機制來使得源 observable 建立出適當型別的 observable 。不需要在返回的 observable 上呼叫 connect 方法,並且在 selector 函式的作用域中會共享源 observable 。

這意味著 multicast (以及 publish) 操作符可以用來輕鬆實現源 observable 的本地共享。

使用 publish 進行本地共享

我們來看看使用 publish 的示例。

RxJS 引入了 defaultIfEmpty 操作符,它接收一個值,如果源 observable 為空的話,會將這個值發出。有時候,能夠指定一個預設 observable 的話要比指定單個值有用得多,那麼讓我們來實現一個 defaultObservableIfEmpty 函式,它可以與 let 操作符一起使用。

下面的彈珠圖展示了源 observable 為空時它的行為:

[譯] RxJS: multicast 操作符的祕密

RxJS 引入了 isEmpty 操作符,當源 observable 完成時,它會發出布林值以標識源 observable 是否為空。但是,要在 defaultObservableIfEmpty 實現中使用它的話,需要共享源 observable,因為需要發出值的通知,而 isEmpty 無法做到這點。publish 操作符使得源 observable 的共享變得簡單,實現如下所示:

function defaultObservableIfEmpty<T>(
  defaultObservable: Observable<T>
): (source: Observable<T>) => Observable<T> {

  return source => source.publish(shared => shared.merge(
    shared.isEmpty().mergeMap(empty => empty ?
      defaultObservable :
      Observable.empty<T>()
    )
  ));
}
複製程式碼

傳給 publishselector 函式接收共享的源 observable 。selector 返回的 observable 是由共享源 observable 和根據源 observable 是否為空得到的 observable (如果源 observable 為空,則為傳入的預設 observable,否則為空 observable) 的組合而成。

源 observable 的共享完全是由 publish 管理的。使用 selector 函式,就能夠根據需要多次訂閱共享 observable,而不會影響源 observable 後面的訂閱。

使用 multicast 進行本地共享

我們來看另一個示例,這次使用 multicast

RxJS 引入了 takeWhile 操作符,它返回的 observable 會發出源 observable 的值,直到不滿足給定條件的值出現,此刻 observable 完成。不滿足條件的那個值不會被髮出。我們來實現一個 takeWhileInclusive 函式,它可以與 let 操作符一起使用。

下面的彈珠圖展示了值不滿足條件時的行為:

[譯] RxJS: multicast 操作符的祕密

可以使用 takeWhile 操作符作為基礎來實現,當不滿足條件時,只需要再連線不滿足條件的那個值即可。要在 takeWhile 返回的 observable 完成後取得這個值,可以使用 ReplaySubject:

function takeWhileInclusive<T>(
  predicate: (value: T) => boolean
): (source: Observable<T>) => Observable<T> {

  return source => source.multicast(
    () => new ReplaySubject<T>(1),
    shared => shared
      .takeWhile(predicate)
      .concat(shared.take(1).filter(t => !predicate(t)))
  );
}
複製程式碼

這裡使用了緩衝區大小為1的 ReplaySubject 來共享源 observable 。當 takeWhile 操作符返回的 observable 完成時,共享的 observable 是串聯的,使用 take(1) 可以確保只考慮重放的值,而 filter 可以確保只有當不滿足條件時才進行追加。

這種方式可靠嗎?

RxJS 5 是相當新的庫,它的文件扔在進行中,所以這種方式還沒有在文件中出現,只是在內部使用。公開的 TypeScript 簽名指出了並非永遠返回的是 ConnectableObservable,也有相對應的單元測試

RxJS 通常比較靈活,因此實現這些函式還有其他方式,但上面的示例說明了當需要本地共享源 observable 時,publishmulticast 簡單易用,值得考慮。

相關文章