web-dev-qa-db-fra.com

RxJS prendWhile mais inclut la dernière valeur

J'ai un pipeline RxJS5 qui ressemble à ceci

Rx.Observable.from([2, 3, 4, 5, 6])
  .takeWhile((v) => { v !== 4 })

Je souhaite conserver l'abonnement jusqu'à ce que je voie 4, mais je souhaite également inclure le dernier élément 4 dans le résultat. Ainsi, l'exemple ci-dessus devrait être

2, 3, 4

Cependant, selon le document officiel , takeWhile, l'opérateur n'est pas inclusif. Ce qui signifie que lorsqu'il rencontre l'élément qui ne correspond pas au prédicat que nous avons donné, il termine le flux immédiatement sans le dernier élément. En conséquence, le code ci-dessus sera en sortie

2, 3

Ma question est donc la suivante: quel est le moyen le plus simple de réaliser takeWhile tout en émettant le dernier élément avec RxJS?

14
Fang-Pen Lin

Il existe déjà un PR ouvert qui ajoute un paramètre optionnel inclusive à takeWhile: https://github.com/ReactiveX/rxjs/pull/4115

Il existe au moins deux solutions de contournement possibles:

  1. en utilisant concatMap():

    of('red', 'blue', 'green', 'orange').pipe(
      concatMap(color => {
        if (color === 'green') {
          return of(color, null);
        }
        return of(color);
      }),
      takeWhile(color => color),
    )
    
  2. Utilisation de multicast():

    of('red', 'blue', 'green', 'orange').pipe(
      multicast(
        () => new ReplaySubject(1),
        subject => subject.pipe(
          takeWhile((c) => c !== 'green'),
          concat(subject.take(1),
        )
      ),
    )
    

J'utilise également cet opérateur et je l'ai donc intégré à mon propre ensemble d'opérateurs RxJS 5 supplémentaires: https://github.com/martinsik/rxjs-extra#takewhileinclusive

Cet opérateur a également été abordé dans ce numéro de RxJS 5: https://github.com/ReactiveX/rxjs/issues/2420

Jan 2019: Mise à jour pour RxJS 6

16
martin

Si votre comparaison est telle que vous savez exactement quel est le dernier élément (comme pour !==), vous pouvez le rajouter vous-même:

Rx.Observable.from([2, 3, 4, 5, 6])
  .takeWhile((v) => v !== 4)
  .concat(Rx.Observable.of(4))
  .subscribe(console.log)
3
Federico Galassi

Je suis tombé sur le même problème, j'avais besoin du dernier élément à inclure. J'ai donc choisi de garder une référence à l'abonnement et de vous désabonner au sein de onNext callback lorsque la condition était remplie. En utilisant votre exemple de code, ce serait:

const subscription = Observable.of('red', 'blue', 'green', 'orange')
  .subscribe(color => {
    // Do something with the value here
    if (color === 'green') {
      subscription.unsubscribe()
    }
  }) 

Cela a fonctionné pour moi parce que cela a également amené la source observable à arrêter d'émettre, ce qui est ce dont j'avais besoin dans mon scénario ... Je réalise que je n'utilise pas l'opérateur takeWhile mais l'objectif principal est atteint et sans solution de rechange ni code supplémentaire . Je ne suis pas partisan de forcer les choses à fonctionner d'une manière pour laquelle elles n'étaient pas conçues. Les inconvénients de ceci sont:

  • Si d’autres observateurs sont abonnés, la source continuera à émettre.
  • La onCompleted ne sera pas appelée pour une raison quelconque si le dernier observateur se désabonne, mais j’ai vérifié que la source cesse d’émettre.
2
Mauricio Quiroga

Vous pouvez utiliser endWith(value) qui (contrairement à beaucoup de code RxJS) est très bien auto-documentant.

const example = source.pipe(
                            takeWhile(val => val != 4), 
                            endWith(4));

PS. Notez également que takeUntil ne prend pas de prédicat. Par conséquent, vous ne pouvez pas utiliser cet opérateur pour résoudre ce problème. C'est une signature de méthode totalement différente.

Documents officiels:https://rxjs-dev.firebaseapp.com/api/operators/endWith

https://stackblitz.com/edit/TypeScript-pvuawt

1
Simon_Weaver

Dans mon cas, je n’étais pas en mesure de prédire quelle serait la valeur finale. Je voulais aussi simplement une solution impliquant des opérateurs simples et communs, et je voulais quelque chose que je puisse réutiliser, pour que je ne puisse pas compter sur les valeurs étant la vérité. La seule chose à laquelle je pouvais penser était de définir mon propre opérateur de la manière suivante:

import { pipe, from } from 'rxjs';
import { switchMap, takeWhile, filter, map } from 'rxjs/operators';

export function doWhile<T>(shouldContinue: (a: T) => boolean) {
  return pipe(
    switchMap((data: T) => from([
      { data, continue: true },
      { data, continue: shouldContinue(data), exclude: true }
    ])),
    takeWhile(message => message.continue),
    filter(message => !message.exclude),
    map(message => message.data)
  );
}

C'est un peu bizarre, mais cela fonctionne pour moi et je peux l'importer et l'utiliser.

0
Michael Pearson