rxjs switchMap нужно возвращать подписанную наблюдаемую - PullRequest
0 голосов
/ 24 января 2019

Вот здесь требование:

Когда нажимаете кнопку запуска, событие emit раз в 100 мс, каждый emit соответствует обновлению пользовательского интерфейса.Когда x раз излучение завершено, это вызовет окончательное обновление пользовательского интерфейса, выглядело просто, верно?

Вот мой код:

const start$ = fromEvent(document.getElementById('start'), 'click')
const intervel$ = interval(100)
    .pipe(
        take(x),
        share()
    )
var startLight$ = start$
    .pipe(
        switchMap(() => {
            intervel$
                .pipe(last())
                .subscribe(() => {
                    // Update UI
                })
            return intervel$
        }),
        share()
    )
startLight$
    .subscribe(function (e) {
        //Update UI
    })

Очевидно, подписка внутри switchMap является анти-паттерном,поэтому я попытался провести рефакторинг своего кода:

const startInterval$ = start$
    .pipe(
        switchMapTo(intervel$),
    )
startInterval$.pipe(last())
    .subscribe(() => {
        //NEVER Receive value
    })

const startLight$ = startInterval$.pipe(share()) 

Проблема в том, что intervel $ stream генерируется внутри switchMap и не может быть доступен снаружи, вы можете получить доступ только к потоку, который генерирует интервал $, то есть start$, который никогда не завершается!

Есть ли более разумный способ справиться с такой проблемой, или это было внутреннее ограничение rxjs?

Ответы [ 2 ]

0 голосов
/ 24 января 2019

Вы были очень близки.Используйте last () внутри intervel $, чтобы отправить только последний подписчик ниже.Рабочая StackBlitz .Вот подробности из StackBlitz:

const start$ = fromEvent(document.getElementById('start'), 'click');
const intervel$ = interval(100)
    .pipe(
        tap(() => console.log('update UI')), // Update UI here
        take(x),
        last()
    );

const startInterval$ = start$
    .pipe( switchMapTo(intervel$));

startInterval$
    .subscribe(() => {
        console.log('will run once');
    });

Обновление

Если вы не хотите использовать tap(), то вы можете просто заставить start $ закончить, взяв только первую эмиссию изатем завершается либо take(1), либо first(). Вот новый StackBlitz , показывающий это.

const start$ = fromEvent(document.getElementById('start'), 'click')
    .pipe(
      first()
    );
const intervel$ = interval(100)
    .pipe( 
      take(x) 
    );

const startInterval$ = start$
    .pipe( 
      switchMapTo(intervel$)
    );

startInterval$
    .subscribe(
      () => console.log('Update UI'),
      err => console.log('Error ', err),
      () => console.log('Run once at the end')
    );

Недостатком этого подхода (или любого подхода, дополняющего наблюдаемое) является то, что после его завершения он не будет использоваться повторно.Так, например, многократное нажатие на кнопку в новом StackBlitz не сработает.Какой подход использовать (первый, на который можно нажимать снова и снова, или тот, который завершается) зависит от результатов, которые вам нужны.

Еще один вариант

Создание двух intervel$ наблюдаемых,один для промежуточных обновлений пользовательского интерфейса и один для последнего.Объедините их вместе и выполняйте только обновление пользовательского интерфейса в подписке. StackBlitz для этой опции

код:

const start$ = fromEvent(document.getElementById('start'), 'click')
const intervel1$ = interval(100)
    .pipe( 
      take(x)
    );
const intervel2$ = interval(100)
    .pipe(
      take(x+1),
      last(),
      mapTo('Final')
    );

const startInterval$ = start$
    .pipe( 
      switchMapTo(merge(intervel1$, intervel2$))
    );

startInterval$
    .subscribe(
        val => console.log('Update UI: ', val)
    );

Более идиоматический способ, та же логика, что и у предыдущего (By Guichi)

import { switchMapTo, tap, take, last, share, mapTo } from 'rxjs/operators';
import { fromEvent, interval, merge } from 'rxjs';

const x = 5;

const start$ = fromEvent(document.getElementById('start'), 'click');


const intervel$ = interval(100);

const intervel1$ = intervel$
  .pipe(
    take(x)
  );
const intervel2$ = intervel1$
  .pipe(
    last(),
    mapTo('Final')
  );

const startInterval$ = start$
  .pipe(
    switchMapTo(merge(intervel1$, intervel2$))
  );

startInterval$
  .subscribe(
    val => console.log('Update UI: ', val)
  );

Отражение

Ключевая проблема исходного вопроса состоит в том, чтобы «использовать одно и то же наблюдаемое по-разному», то есть во время прогресса и финала.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * merge - вполне приличная логическая модель для решения этой проблемы

0 голосов
/ 24 января 2019

Поместите свою логику обновления в switchMap и tap(), тап будет выполняться несколько раз, и только подписка будет выполняться только в последний раз ()

const startInterval$ = start$
    .pipe(
        switchMap(()=>intervel$.pipe(tap(()=>//update UI),last()),
    )
startInterval$
    .subscribe(() => {
//    will run one time
    })
...