瞭解rxjs中的defer

阿古達木發表於2021-10-16

下面介紹一個少有人知道的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

程式碼: https://stackblitz.com/edit/r...

相關文章