Як змусити одну спостережувану послідовність чекати завершення іншої, перш ніж випускати?


86

Скажімо, у мене є Observable, ось так:

var one = someObservable.take(1);

one.subscribe(function(){ /* do something */ });

Потім у мене є друга Observable:

var two = someOtherObservable.take(1);

Тепер я хочу , subscribe()щоб two, але я хочу , щоб переконатися , що зробити oneвже завершено до twoпередплатник обпалюють.

Який метод буферизації я можу використовувати twoдля того, щоб другий чекав завершення першого?

Я припускаю, що я хочу зробити паузу twoдо oneзавершення.


1
Я вважаю, що відповідь на це - метод .exhaustMap (), однак я б не прикидався, що знаю, як його реалізувати - повний опис тут: blog.angular-university.io/rxjs-higher-order-mapping
Пітер Ніксі

Відповіді:


53

Я можу придумати кілька способів

import {take, publish} from 'rxjs/operators'
import {concat} from 'rxjs'

//Method one

var one = someObservable.pipe(take(1));
var two = someOtherObservable.pipe(take(1));
concat(one, two).subscribe(function() {/*do something */});

//Method two, if they need to be separate for some reason
var one = someObservable.pipe(take(1));
var two = someOtherObservable.pipe(take(1), publish());
two.subscribe(function(){/*do something */});
one.subscribe(function(){/*do something */}, null, two.connect.bind(two));

1
Я закінчив з використанням pauseі resumeзамість publishі connect, але приклад два, по суті , маршрут я взяв.
Стівен

1
Чи завжди цей метод змушує перше спостережуване ( one) вирішити перед другим ( two) всередині функції Subscribe () -?
Джон

Чому б не використовувати Observable.forkJoin()? Дивіться це посилання learnrxjs.io/operators/combination/forkjoin.html
mspasiuk

16
@mspasiuk відповідно до вимог до ОП, вони хотіли, щоб другий підписався лише після завершення першого. forkJoinпередплачує одночасно.
paulpdaniels

17

Якщо ви хочете переконатися, що порядок виконання збережено, ви можете використовувати flatMap як наступний приклад

const first = Rx.Observable.of(1).delay(1000).do(i => console.log(i));
const second = Rx.Observable.of(11).delay(500).do(i => console.log(i));
const third = Rx.Observable.of(111).do(i => console.log(i));

first
  .flatMap(() => second)
  .flatMap(() => third)
  .subscribe(()=> console.log('finished'));

Результатом буде:

"1"
"11"
"111"
"finished"

15

skipUntil () з last ()

skipUntil: ігнорувати випущені елементи, доки не викине інше спостережуване

last: видати останнє значення з послідовності (тобто зачекати, поки воно завершиться, потім випустити)

Зауважте, що будь-що, що випромінюється зі спостережуваного, передається в skipUntil, скасовує пропуск, саме тому нам потрібно додати last()- чекати завершення потоку.

main$.skipUntil(sequence2$.pipe(last()))

Офіційне: https://rxjs-dev.firebaseapp.com/api/operators/skipUntil


Можлива проблема: Зверніть увагу, що last()сама по собі помилка, якщо нічого не видається. last()Оператор дійсно є defaultпараметр , але тільки при використанні в поєднанні з предикатом. Я думаю, якщо ця ситуація є проблемою для вас (якщо sequence2$може завершитися без випромінювання), тоді одна з них повинна працювати (наразі не перевірена):

main$.skipUntil(sequence2$.pipe(defaultIfEmpty(undefined), last()))
main$.skipUntil(sequence2$.pipe(last(), catchError(() => of(undefined))

Зверніть увагу, що undefinedце допустимий елемент, який потрібно випустити, але насправді може мати будь-яке значення. Також зверніть увагу, що це труба, прикріплена до неї, sequence2$а не main$труба.


Дуже незграбна демонстрація: angular-vgznak.stackblitz.io Вам потрібно клацнути, щоб відкрити лоток консолі
Simon_Weaver

Ваш синтаксис неправильний. skipUntil не можна безпосередньо приєднати до спостережуваного, інакше ви отримаєте таку помилку: 'Властивість' skipUntil 'не існує у типі' Observable <any> '. Спочатку потрібно пропустити його через .pipe ()
London804,

Так, це стара відповідь, перш ніж потрібно було трубу. Дякуємо, що згадали. Я б оновлював це зараз, але я на своєму телефоні. Не соромтеся редагувати відповідь.
Simon_Weaver

13

Ось ще одна можливість скористатися перемикачем результатів switchMap

var one$ = someObservable.take(1);
var two$ = someOtherObservable.take(1);
two$.switchMap(
    /** Wait for first Observable */
    () => one$,
    /** Only return the value we're actually interested in */
    (value2, value1) => value2
  )
  .subscribe((value2) => {
    /* do something */ 
  });

Оскільки селектор результатів switchMap знецінений, ось оновлена ​​версія

const one$ = someObservable.pipe(take(1));
const two$ = someOtherObservable.pipe(
  take(1),
  switchMap(value2 => one$.map(_ => value2))
);
two$.subscribe(value2 => {
  /* do something */ 
});

8

Ось спосіб багаторазового використання (це машинопис, але ви можете адаптувати його до js):

export function waitFor<T>(signal: Observable<any>) {
    return (source: Observable<T>) =>
        new Observable<T>(observer =>
            signal.pipe(first())
                .subscribe(_ =>
                    source.subscribe(observer)
                )
        );
}

і ви можете використовувати його як будь-який оператор:

var two = someOtherObservable.pipe(waitFor(one), take(1));

В основному це оператор, який відкладає підписку на джерело спостережуваного, доки спостережуваний сигнал не випустить першу подію.


6

Якщо друге спостережуване є гарячим , є інший спосіб зробити паузу / відновити :

var pauser = new Rx.Subject();
var source1 = Rx.Observable.interval(1000).take(1);
/* create source and pause */
var source2 = Rx.Observable.interval(1000).pausable(pauser);

source1.doOnCompleted(function () { 
  /* resume paused source2 */ 
  pauser.onNext(true);
}).subscribe(function(){
  // do something
});

source2.subscribe(function(){
  // start to recieve data 
});

Також ви можете використовувати буферну версію pausableBuffered для збереження даних під час увімкненої паузи.


2

Ось ще один, але я почуваюся більш прямолінійним та інтуїтивним (або, принаймні, природним, якщо ви звикли до обіцянок), підхід. В основному, ви створюєте Observable за допомогою Observable.create()обгортання oneта twoяк єдиний Observable. Це дуже схоже на те, як Promise.all()може працювати.

var first = someObservable.take(1);
var second = Observable.create((observer) => {
  return first.subscribe(
    function onNext(value) {
      /* do something with value like: */
      // observer.next(value);
    },
    function onError(error) {
      observer.error(error);
    },
    function onComplete() {
      someOtherObservable.take(1).subscribe(
        function onNext(value) {
          observer.next(value);
        },
        function onError(error) {
          observer.error(error);
        },
        function onComplete() {
          observer.complete();
        }
      );
    }
  );
});

Отже, що тут відбувається? Спочатку ми створюємо новий Observable. Функція, передана Observable.create(), з доречним іменем onSubscription, передається спостерігачеві (побудована з параметрів, до яких ви переходите subscribe()), яка схожа на resolveтаreject об'єднується в єдиний об'єкт під час створення нового Promise. Ось так ми змушуємо магію працювати.

У Росії onSubscriptionми підписуємось на перший Observable (у наведеному вище прикладі це називалося one). Як ми поводимось nextі errorзалежить від вас, але за замовчуванням, наведеному в моєму зразку, слід відповідати загалом. Однак, коли ми отримуємо completeподію, це означаєone , що тепер зроблено, ми можемо підписатися на наступний Observable; тим самим вистрілює другий Observable після завершення першого.

Приклад спостерігача, наданий для другого спостережуваного, досить простий. В основному, secondзараз діє як те, що ви очікували twoби діяти як в ОП. Більш конкретно, secondбуде випромінюватися перше і єдине перше значення, випущене параметром someOtherObservable(черезtake(1) ), а потім завершене, припускаючи, що помилки немає.

Приклад

Ось повний робочий приклад, який ви можете скопіювати / вставити, якщо хочете побачити, як мій приклад працює в реальному житті:

var someObservable = Observable.from([1, 2, 3, 4, 5]);
var someOtherObservable = Observable.from([6, 7, 8, 9]);

var first = someObservable.take(1);
var second = Observable.create((observer) => {
  return first.subscribe(
    function onNext(value) {
      /* do something with value like: */
      observer.next(value);
    },
    function onError(error) {
      observer.error(error);
    },
    function onComplete() {
      someOtherObservable.take(1).subscribe(
        function onNext(value) {
          observer.next(value);
        },
        function onError(error) {
          observer.error(error);
        },
        function onComplete() {
          observer.complete();
        }
      );
    }
  );
}).subscribe(
  function onNext(value) {
    console.log(value);
  },
  function onError(error) {
    console.error(error);
  },
  function onComplete() {
    console.log("Done!");
  }
);

Якщо ви дивитесь консоль, надрукується наведений вище приклад:

1

6

Готово!


Це був прорив, який мені знадобився, щоб створити власний користувацький оператор «кластер (T, X, D)», який обробляє лише перші X, що викидаються в межах часового проміжку T від джерела, і видає результати, віддалені від затримки D. Дякую!
winkim00

Я рада, що це допомогло, це було дуже просвітницько, коли я це теж зрозумів.
c1moore

2

Ось спеціальний оператор, написаний на TypeScript, який чекає сигналу перед тим, як видавати результати:

export function waitFor<T>(
    signal$: Observable<any>
) {
    return (source$: Observable<T>) =>
        new Observable<T>(observer => {
            // combineLatest emits the first value only when
            // both source and signal emitted at least once
            combineLatest([
                source$,
                signal$.pipe(
                    first(),
                ),
            ])
                .subscribe(([v]) => observer.next(v));
        });
}

Ви можете використовувати його так:

two.pipe(waitFor(one))
   .subscribe(value => ...);

1
приємний візерунок! Ви навіть можете зробити three.pipe (waitFor (one), waitFor (two), take (1))
Девід Рінк,

1

ну, я знаю, що це досить давно, але я думаю, що вам може знадобитися:

var one = someObservable.take(1);

var two = someOtherObservable.pipe(
  concatMap((twoRes) => one.pipe(mapTo(twoRes))),
  take(1)
).subscribe((twoRes) => {
   // one is completed and we get two's subscription.
})

0

Ви можете використовувати результат, випущений з попереднього Observable завдяки оператору mergeMap (або його псевдоніму flatMap ), наприклад:

 const one = Observable.of('https://api.github.com/users');
 const two = (c) => ajax(c);//ajax from Rxjs/dom library

 one.mergeMap(two).subscribe(c => console.log(c))

звідси: learnrxjs.io/learn-rxjs/operators/transformation/mergemap - "Якщо порядок випуску та підписки внутрішніх спостережуваних важливий, спробуйте concatMap!"
gsziszi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.