web-dev-qa-db-fra.com

Angular 2 avec RxJS - prendre (1) vs premier ()

J'ai trouvé peu d'implémentation de Auth Guards utilisant take(1). Sur mon projet, j’ai utilisé first() pour répondre à mes besoins. Cela fonctionne-t-il de la même manière? Ou l'un d'entre eux pourrait avoir des avantages ou plus.

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/first';
import { Observable } from 'rxjs/Observable';

import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AngularFire } from 'angularfire2';

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private angularFire: AngularFire, private router: Router) { }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
        return this.angularFire.auth.map(
            (auth) =>  {
                if (auth) {
                    this.router.navigate(['/dashboard']);
                    return false;
                } else {
                    return true;
                }
            }
        ).first(); // Just change this to .take(1)
    }
}
75
Karuban

Les opérateurs first() et take(1) ne sont pas identiques.

L'opérateur first() prend une fonction facultative predicate et émet une notification error lorsqu'aucune valeur ne correspond à la fin de la source.

Par exemple cela va émettre une erreur:

import { EMPTY, range } from 'rxjs';
import { first, take } from 'rxjs/operators';

EMPTY.pipe(
  first(),
).subscribe(console.log, err => console.log('Error', err));

... ainsi que cette:

range(1, 5).pipe(
  first(val => val > 6),
).subscribe(console.log, err => console.log('Error', err));

Bien que cela corresponde à la première valeur émise:

range(1, 5).pipe(
  first(),
).subscribe(console.log, err => console.log('Error', err));

Par contre, take(1) ne prend que la première valeur et se termine. Aucune autre logique n'est impliquée.

range(1, 5).pipe(
  take(1),
).subscribe(console.log, err => console.log('Error', err));

Ensuite, avec une source vide Observable, aucune erreur ne sera émise:

EMPTY.pipe(
  take(1),
).subscribe(console.log, err => console.log('Error', err));

Jan 2019: Mise à jour pour RxJS 6

120
martin

Astuce: utilisez uniquement first() si:

  • Vous considérez que zéro élément émis est une condition d’erreur (par exemple, terminer avant d’émettre) AND s’il ya plus de 0% de chance que vous l’ayez gérée normalement
  • OU Vous savez à 100% que la source observable émettra 1+ éléments (donc ne peut jamais lancer).

S'il n'y a aucune émission et que vous ne la manipulez pas explicitement (avec catchError), cette erreur se propage, ce qui peut éventuellement causer un problème inattendu ailleurs et peut être assez difficile à détecter, en particulier si elle provient d'un utilisateur final.

Vous êtes plus sûr désactivé avec take(1) pour la plupart, à condition que:

  • Vous êtes d'accord avec take(1) n'émettre rien si la source se termine sans émission.
  • Vous n'avez pas besoin d'utiliser un prédicat en ligne (par exemple. first(x => x > 10))

Remarque: Vous pouvez utiliser un prédicat avec take(1) comme ceci: .pipe( filter(x => x > 10), take(1) ). Il n'y a pas d'erreur avec cela si rien n'est jamais supérieur à 10.

Qu'en est-il de single()

Si vous voulez être encore plus strict et interdire deux émissions, vous pouvez utiliser single() quelles erreurs s'il y a zéro ou plus de 2 émissions. Là encore, vous devrez gérer les erreurs dans ce cas.

Conseil: Single peut parfois être utile si vous voulez vous assurer que votre chaîne d'observables ne fait pas un travail supplémentaire, comme appeler un service http deux fois et émettre deux observables. Ajouter single à la fin du tuyau vous permettra de savoir si vous avez commis une telle erreur. Je l'utilise dans un 'gestionnaire de tâches' où vous transmettez une tâche observable qui ne devrait émettre qu'une seule valeur. Je passe donc la réponse par single(), catchError() pour garantir un bon comportement.


Pourquoi ne pas toujours utiliser first() au lieu de take(1)?

alias. Comment first peut-il potentiellement causer plus d'erreurs?

Si vous avez un observable qui prend quelque chose d'un service et qui le transfère ensuite à travers first(), tout devrait bien se passer la plupart du temps. Mais si quelqu'un se présente pour désactiver le service pour une raison quelconque - et le modifie pour qu'il émette of(null) ou NEVER, tous les opérateurs first() situés en aval commenceront à générer des erreurs.

Maintenant, je me rends compte que cela pourrait être exactement ce que vous voulez - d’où pourquoi ce n’est qu’un pourboire. L’opérateur first m’a séduit parce que cela sonnait un peu moins "maladroit" que take(1), mais vous devez faire attention aux erreurs de traitement s’il est possible que la source n’émette jamais. Cela dépendra entièrement de ce que vous faites cependant.


Si vous avez une valeur par défaut (constante):

Pensez également à .pipe(defaultIfEmpty(42), first()) si vous avez une valeur par défaut à utiliser si rien n’est émis. Bien entendu, cela ne générerait pas d'erreur car first recevrait toujours une valeur.

Notez que defaultIfEmpty n'est déclenché que si le flux est vide, et non si la valeur de ce qui est émis est null.

12
Simon_Weaver

Il semble que dans RxJS 5.2.0, l’opérateur .first() ait un bug ,

A cause de ce bogue, .take(1) et .first() peuvent se comporter de manière très différente si vous les utilisez avec switchMap:

Avec take(1) vous obtiendrez le comportement attendu:

var x = Rx.Observable.interval(1000)
   .do( x=> console.log("One"))
   .take(1)
   .switchMap(x => Rx.Observable.interval(1000))
   .do( x=> console.log("Two"))
   .subscribe((x) => {})

// In the console you will see:
// One
// Two
// Two
// Two
// Two
// etc...

Mais avec .first() vous obtiendrez un mauvais comportement:

var x = Rx.Observable.interval(1000)
  .do( x=> console.log("One"))
  .first()
  .switchMap(x => Rx.Observable.interval(1000))
  .do( x=> console.log("Two"))
  .subscribe((x) => {})

// In console you will see:
// One
// One
// Two
// One
// Two
// One
// etc... 

Voici un lien vers codepen

9
Artem

Il y a une différence très importante qui n'est mentionnée nulle part.

take (1) émet 1, complète, se désabonne

first () émet 1, se termine, mais ne se désabonne pas.

Cela signifie que votre observable en amont sera toujours chaud après la première (), ce qui n’est probablement pas le comportement attendu.

UPD: Ceci fait référence à RxJS 5.2.0. Ce problème est peut-être déjà résolu.

9
norekhov

Voici trois observables A, B et C avec des diagrammes en marbre pour explorer la différence entre les opérateurs first, take et single:

first vs take vs single operators comparison

* Légende :
--o-- valeur
----! erreur
----| complétion

Jouez avec à https://thinkrx.io/rxjs/first-vs-take-vs-single/.

Ayant déjà toutes les réponses, je voulais ajouter une explication plus visuelle

J'espère que ça aide quelqu'un

7
kos