下面介紹一個少有人知道的observable -- defer,如何使用,什麼時候使用
讀這篇文章之前,你需要對rxjs基礎用法有一定的瞭解
假設我們需要寫一個自定義operator叫做tapOnce。接收一個函式當作引數,只有流的第一次觸發時才執行
function tapOnce(fn: Function) {
let run = false;
return function (source: Observable<any>) {
return source.pipe(
tap((data) => {
if (!run) {
fn(data);
run = true;
}
})
);
};
}
這段程式碼簡單直觀,在tap的基礎上,用了一個變數來控制執行次數,呼叫一下
const test$ = interval(1000).pipe(tapOnce((d) => console.log('?', d)));
test$.subscribe();
// 1s之後列印 ?0
執行很正常,在流第一次觸發的時候列印狗頭。
要是再加一個訂閱者呢?
const test$ = interval(1000).pipe(tapOnce((d) => console.log('?', d)));
test$.subscribe();
test$.subscribe();
// 1s之後列印 ?0
結果只列印了一遍,這是因為兩個訂閱者訂閱同一個流,使用同一個run變數。
想要列印兩遍,我們就需要一個能夠在訂閱時才建立流的功能。
defer就是用來做這件事的
改進一下
function tapOnce(fn: Function) {
return function (source: Observable<any>) {
return defer(() => {
let run = false;
return source.pipe(
tap((data) => {
if (!run) {
fn(data);
run = true;
}
})
);
});
};
}
defer接收一個返回型別為observable的函式。只有當defer被訂閱了,函式才會執行。而不是在建立時。然後利用js閉包,讓每個訂閱者有自己的作用域。
通過簡單的抽象類看一下defer到底是怎麼實現的
function defer(observableFactory: () => ObservableInput<any>) {
return new Observable(subscriber => {
const source = observableFactory();
return source.subscribe(subscriber);
});
}
defer返回一個新的observable。當有訂閱者訂閱的時候,就會執行工廠方法,建立並返回新的observalbe。
看看defer還能在什麼場景下發揮作用,假設有一個這樣的需求,每次訂閱的時候返回一個隨機數
const randNum = of(Math.random());
randNum.subscribe(console.log);
randNum.subscribe(console.log);
這裡每一個訂閱者列印的值是一樣的,我們可以用defer改進一下
const randNum = defer(() => of(Math.random()));
randNum.subscribe(console.log);
randNum.subscribe(console.log);
// 等同於這種寫法
const randNum2 = () => of(Math.random());
randNum2().subscribe(console.log);
randNum2().subscribe(console.log);
另一種場景是我們想要延遲一下promise的執行時間。當有訂閱者的時候,promise才執行。實現一個lazyPromise
// 此時console.log('promise')已經執行
const promise = new Promise((resolve) => {
console.log('promise');
setTimeout(() => resolve('promise'), 1000);
});
// console.log('lazy promise');只有當被訂閱才執行
const lazyPromise = defer(() => {
return new Promise((resolve) => {
console.log('lazy promise');
setTimeout(() => resolve('promise'), 1000);
});
});
lazyPromise.subscribe(console.log);
Promises天生就是熱流,無視訂閱者。我們可以通過defer,把promise變成一個Observable-like