2

The following introduces an observable-defer that few people know, how to use it, and when to use it
reading this article, you need to have a certain understanding of the basic usage of


Suppose we need to write a custom operator called tapOnce. Receive a function as a parameter and execute it only when the stream is triggered for the first time

function tapOnce(fn: Function) {
  let run = false;
  return function (source: Observable<any>) {
    return source.pipe(
      tap((data) => {
        if (!run) {
          fn(data);
          run = true;
        }
      })
    );
  };
}

This code is simple and intuitive. On the basis of tap, a variable is used to control the number of executions. Call it

const test$ = interval(1000).pipe(tapOnce((d) => console.log('🐶', d)));

test$.subscribe();
// 1s之后打印 🐶0

The operation is normal, and the dog head is printed when the stream is triggered for the first time.
What if you add another subscriber?

const test$ = interval(1000).pipe(tapOnce((d) => console.log('🐶', d)));

test$.subscribe();
test$.subscribe();
// 1s之后打印 🐶0

The result is only printed once, because two subscribers subscribe to the same stream and use the same run variable.
To print twice, we need a function that can create a stream only when subscribing.
Defer is used to do this
Improve

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 receives a function whose return type is observable. Only when defer is subscribed, the function will be executed. Not at creation time. Then use the js closure to allow each subscriber to have its own scope.

Look at how defer is implemented through a simple abstract class

function defer(observableFactory: () => ObservableInput<any>) {
  return new Observable(subscriber => {
    const source = observableFactory();
    return source.subscribe(subscriber);
  });
}

Defer returns a new observable. When a subscriber subscribes, the factory method will be executed to create and return a new observalbe.

See what other scenarios defer can play in. Assuming there is such a demand, a random number is returned every time you subscribe

const randNum = of(Math.random());
 
randNum.subscribe(console.log);
randNum.subscribe(console.log);

Here, the value printed by each subscriber is the same, we can use defer to improve it

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);

Another scenario is that we want to delay the execution time of the promise. When there are subscribers, the promise is executed. Implement a 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 are inherently hot, ignoring subscribers. We can turn promise into an Observable-like through defer

Code: https://stackblitz.com/edit/rxjs-zw5ujo?file=index.ts


阿古达木
574 声望17 粉丝

牛逼的工程师就是能用简单的代码和思路写出复杂的功能