Знакомимся с defer

от автора

В прошлой статье мы рассмотрели как создавать свои операторы RxJS. Сейчас я хочу поговорить о малоизвестном операторе создания — defer — и объяснить как можно использовать его для некоторых ситуаций

Допустим нужно сделать оператор, который берет функцию и выполняет ее лишь один раз, при первом получении значения. Реализуем его под именем tapOnce

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

Код понятен — tap используется для запуска функции, флаг run нужен чтобы сделать это только один раз. Теперь используем оператор.

const source = interval(5000).pipe(   tapOnce(() => console.log('+') ));  source.subscribe(console.log);

Все работает, плюсик выводится в консоль только при первом эмите. А теперь добавим подписчиков.

const source = interval(5000).pipe(   tapOnce(() => console.log('+') ));  source.subscribe(console.log); source.subscribe(console.log);

Если посмотреть в консоль — там только один плюсик. Проблема в том что оба подписчика используют одно и то же лексическое окружение и ссылаются на ту же переменную run. Нужен способ отсрочить создание потока до момента, когда кто-нибудь подпишется.

Поможет defer

import { defer } from 'rxjs';  function tapOnce<T>(fn: Function): OperatorFunction<T, T> {   return function(source: Observable<any>) {     return defer(() => {       let run = false;       return source.pipe(         tap(() => {           if (!run) {             fn();             run = true;           }         })       );     });   }; }

Оператор defer принимает функцию, которая должна вернуть ObservableInput. Код внутри defer выполнится только при подписке, а не во время создания. Используя этот подход и благодаря замыканию js, каждый подписчик использует свое собственное лексическое окружение.

Создадим свою простую реализацию defer для лучшего понимания.

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

defer возвращает новый поток, который создается в момент подписки функцией-фабрикой, и будет использоваться как источник.

Вот еще примеры, где будет полезен defer. Скажем у нас есть выражение, которое надо посчитать когда кто-нибудь подписывается. Например

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

В этом примере каждый подписчик получит одно и тоже случайное значение. Можно поправить, чтобы выражение посчиталось при подписке, а не при объявлении.

const randNum = defer(() => of(Math.random()));  randNum2.subscribe(console.log); randNum2.subscribe(console.log);  // The same concept as using a function const randNum = () => of(Math.random()); randNum2().subscribe(console.log); randNum2().subscribe(console.log);

Другой пример это когда нужно отсрочить выполнение промиса.

// This already executing regardless the numbers of handlers const promise = new Promise(resolve => {   resolve(); });  // Deferring the creation of the promise until someone subscribes const promiseDefered = defer(() => {   return new Promise(resolve => {     resolve();   }); });  promiseDefered.subscribe(console.log);

Промисы же выполняются сразу независимо от числа слушателей. Можно сделать промис похожим на поток (т.е. ленивым) используя defer.

ссылка на оригинал статьи https://habr.com/ru/post/492948/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *