web-dev-qa-db-fra.com

Comment faire en sorte qu'une séquence RxJS Observable attende la fin d'une autre séquence avant d'émettre?

Dites que j'ai un observable, comme suit:

var one = someObservable.take(1);

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

Ensuite, j'ai un deuxième observable:

var two = someOtherObservable.take(1);

Maintenant, je veux m'abonner à two, mais je veux m'assurer que one est terminé avant le renvoi de l'abonné two. Quel type de méthode de mise en mémoire tampon puis-je utiliser sur two pour faire en sorte que la seconde attende la fin de la première?

Je suppose que je cherche à mettre en pause two jusqu'à ce que one soit terminé.

33
Stephen

Quelques façons auxquelles je peux penser

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

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

Si vous voulez vous assurer que l'ordre d'exécution est conservé, vous pouvez utiliser flatMap comme exemple suivant. 

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

Le résultat serait: 

"1"
"11"
"111"
"finished"
7
Entrodus

skipUntil () avec last ()

skipUntil: ignore les éléments émis jusqu'à ce qu'un autre observable ait émis

last: émet la dernière valeur d'une séquence (c.-à-d. attendez qu'elle se termine, puis émettez)

Notez que l'observable passé à skipUntil doit simplement émettre quoi que ce soit pour annuler le saut, c'est pourquoi nous devons mettre last (). 

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

Officiel: https://rxjs-dev.firebaseapp.com/api/operators/skipUntil


Problème possible: Notez que last() par lui-même affichera une erreur si rien n'est émis. L'opérateur last() a un paramètre default mais uniquement lorsqu'il est utilisé avec un prédicat. Je pense que si cette situation vous pose un problème (si sequence2$ peut se terminer sans émettre), l'un de ceux-ci devrait fonctionner (pas encore testé):

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

Notez que undefined est un élément valide à émettre, mais pourrait en réalité être n'importe quelle valeur. Notez également qu'il s'agit du canal attaché à sequence2$ et non du tube main$.

7
Simon_Weaver

Voici encore une autre possibilité en tirant parti du sélecteur de résultats de 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 */ 
  });
5
Joe King

Si la seconde observation est hot, il y a une autre façon de faire pause/resume:

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

Vous pouvez également utiliser la version tamponnée pausableBuffered pour conserver les données pendant la pause. 

4
Anton

En voici une autre, mais je me sens plus simple et intuitive (ou du moins naturelle si vous êtes habitué aux promesses), approche. Fondamentalement, vous créez un observable à l'aide de Observable.create() pour envelopper one et two sous la forme d'un observable unique. Ceci est très similaire à la façon dont Promise.all() peut fonctionner.

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

Alors, qu'est-ce qui se passe ici? Tout d'abord, nous créons un nouvel observable. La fonction transmise à Observable.create(), ainsi nommée onSubscription, est transmise à l'observateur (construit à partir des paramètres passés à subscribe()), qui est similaire à resolve et reject combinés dans un seul objet lors de la création d'une nouvelle promesse. C'est ainsi que nous faisons fonctionner la magie.

Dans onSubscription, nous nous abonnons au premier Observable (dans l'exemple ci-dessus, cela s'appelait one). La façon dont nous traitons next et error dépend de vous, mais la valeur par défaut fournie dans mon exemple devrait être appropriée en général. Cependant, lorsque nous recevons l'événement complete, ce qui signifie que one est maintenant terminé, nous pouvons nous abonner au prochain observable. tirant ainsi le deuxième Observable après l’achèvement du premier.

L'exemple d'observateur fourni pour le deuxième observable est assez simple. De manière générale, second agit maintenant comme ce que vous attendriez que two agisse comme dans le PO. Plus précisément, second émettra la première et seule première valeur émise par someOtherObservable (à cause de take(1)) et sera ensuite terminé, en supposant qu’il n’y ait pas d’erreur.

Exemple

Voici un exemple de travail complet que vous pouvez copier/coller si vous voulez voir mon exemple fonctionner dans la vie réelle:

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!");
  }
);

Si vous regardez la console, l'exemple ci-dessus imprimera:

1

6

Terminé!

1
c1moore

Vous pouvez utiliser le résultat émis par Observable précédent grâce à l’opérateur mergeMap (ou son alias flatMap ) comme ceci:

 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))
0
Tktorza

eh bien, je sais que c'est assez vieux, mais je pense que ce dont vous pourriez avoir besoin est:

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
itay oded

Voici une manière réutilisable de le faire (c'est TypeScript mais vous pouvez l'adapter à js):

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

et vous pouvez l'utiliser comme n'importe quel opérateur:

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

Il s’agit essentiellement d’un opérateur qui reporte l’abonnement sur la source observable jusqu’à ce que le signal observable émette le premier événement.

0
Andrei Tătar