瞭解 rxjs 中的 defer

作者:阿古達木

來源:SegmentFault 思否社區

下面介紹一個少有人知道的 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/rxjs-zw5ujo?file=index.ts

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/3N-5lTvNtNewaN9NuKLHbg