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


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.