原文連結: blog.angularindepth.com/rxjs-multic…
本文為 RxJS 中文社群 翻譯文章,如需轉載,請註明出處,謝謝合作!
如果你也想和我們一起,翻譯更多優質的 RxJS 文章以奉獻給大家,請點選【這裡】
multicast
操作符有一個祕密。publish
操作符也是如此,它封裝了 multicast
。這個祕密有時候真的挺好用的。
祕密
multicast
和 publish
的文件中都提到了 ConnectableObservable
。ConnectableObservable
是一種特殊型別的 observable,只有呼叫它的 connect
方法後,它才會開始向訂閱者傳送通知。然而,multicast
和 publish
操作符並非永遠返回 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 引入了 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>()
)
));
}
複製程式碼
傳給 publish
的 selector
函式接收共享的源 observable 。selector
返回的 observable 是由共享源 observable 和根據源 observable 是否為空得到的 observable (如果源 observable 為空,則為傳入的預設 observable,否則為空 observable) 的組合而成。
源 observable 的共享完全是由 publish
管理的。使用 selector
函式,就能夠根據需要多次訂閱共享 observable,而不會影響源 observable 後面的訂閱。
使用 multicast 進行本地共享
我們來看另一個示例,這次使用 multicast
。
RxJS 引入了 takeWhile
操作符,它返回的 observable 會發出源 observable 的值,直到不滿足給定條件的值出現,此刻 observable 完成。不滿足條件的那個值不會被髮出。我們來實現一個 takeWhileInclusive
函式,它可以與 let
操作符一起使用。
下面的彈珠圖展示了值不滿足條件時的行為:
可以使用 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 時,publish
和 multicast
簡單易用,值得考慮。