226 votes

Angular 2 avec RxJS - take(1) vs first()

J'ai trouvé quelques implémentations de Auth Guards qui utilisent take(1) . Sur mon projet, j'ai utilisé first() pour satisfaire mes besoins. Est-ce que cela fonctionne de la même manière ? Ou l'un d'entre eux pourrait avoir des avantages.

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

299voto

Martin Points 1093

Opérateurs first() et take() ne sont pas les mêmes.

Le site first() L'opérateur prend une option predicate et émet un error notification lorsqu'aucune valeur ne correspond à l'achèvement de la source.

Par exemple, ceci émettra 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 ceci :

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

Alors que cela correspondra à la première valeur émise :

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

D'autre part take(1) prend simplement la première valeur et termine. Aucune autre logique n'est impliquée.

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

Alors avec une source Observable vide, il n'émettra pas d'erreur :

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

Janvier 2019 : Mise à jour pour RxJS 6

4 votes

Juste pour info, je n'ai pas dit que first() y take() sont les mêmes en général, ce qui me semble évident, seulement que first() y take(1) sont les mêmes. D'après votre réponse, je ne sais pas si vous pensez qu'il y a encore une différence ?

24 votes

@GünterZöchbauer En fait, leur comportement est différent. Si la source n'émet rien et se termine alors first() envoyer une notification d'erreur pendant que take(1) n'émet simplement rien.

0 votes

@martin, dans certains cas take(1) n'émettra rien signifie que le débogage du code sera plus difficile ?

81voto

Simon_Weaver Points 31141

Conseil : N'utilisez que first() si :

  • Vous considérez que l'émission de zéro élément est une condition d'erreur (par exemple, l'achèvement avant l'émission). ET s'il y a plus de 0 % de chance d'erreur, vous la traitez de manière élégante.
  • OU Vous savez à 100% que l'observable source émettra 1+ items (donc ne peut jamais être lancé). .

S'il n'y a pas d'émissions et que vous ne le manipulez pas explicitement (avec catchError ), cette erreur se propagera vers le haut, causera peut-être un problème inattendu ailleurs et peut être assez difficile à localiser, surtout si elle provient d'un utilisateur final.

Vous êtes plus sûr en utilisant take(1) pour la plupart d'entre eux :

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

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

Qu'en est-il single()

Si vous voulez être encore plus strict, et interdire deux émissions, vous pouvez utiliser single() qui se trompe s'il y a des émissions nulles ou 2+. . 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 de travail supplémentaire comme appeler un service http deux fois et émettre deux observables. Ajout de single au bout du tuyau vous permettra de savoir si vous avez fait une telle erreur. Je l'utilise dans un "task runner" où l'on passe un observable de tâche qui ne doit émettre qu'une seule valeur. single(), catchError() pour garantir un bon comportement.


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

aka. Comment first potentiellement provoquer plus d'erreurs ?

Si vous avez un observable qui prend quelque chose d'un service et le transmet par le biais de first() vous devriez vous en sortir la plupart du temps. Mais si quelqu'un vient à désactiver le service pour une raison quelconque - et le modifie pour qu'il émette of(null) ou NEVER alors toute personne en aval first() les opérateurs commenceraient à lancer des erreurs.

Maintenant je réalise que ça pourrait être exactement ce que vous voulez - d'où le fait que ce n'est qu'un conseil. L'opérateur first m'a séduit parce qu'il sonnait un peu moins "maladroit" que take(1) mais il faut faire attention à la gestion des erreurs s'il y a une chance que la source n'émette pas. Cela dépendra entièrement de ce que vous faites.


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

Considérez également .pipe(defaultIfEmpty(42), first()) si vous avez une valeur par défaut qui devrait être utilisée si rien n'est émis. Ceci ne soulèverait bien sûr pas d'erreur car first recevrait toujours une valeur.

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

3 votes

Sachez que single a plus de différences avec first . 1. Il n'émettra la valeur que sur complete . Cela signifie que si l'observable émet une valeur mais ne se termine jamais, alors single n'émettra jamais de valeur. 2. Pour une raison quelconque, si vous passez une fonction de filtre à single qui ne correspond à rien, il émettra un undefined si la séquence originale n'est pas vide, ce qui n'est pas le cas avec first .

1 votes

Concernant 2. C'était un bogue qui est maintenant corrigé.

57voto

Kos Points 425

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

first vs take vs single operators comparison

* Légende :
--o-- valeur
----! erreur
----| achèvement des travaux

Jouez avec elle à 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 cela aidera quelqu'un

0 votes

La fonction first() se termine-t-elle après avoir reçu la première valeur ?

1 votes

@FernandoGabrieli, oui ! Il se termine immédiatement après l'émission de la première valeur. Sur la viz le | derrière le (0) indique que. Plus de détails à thinkrx.io/rxjs/first

22voto

norekhov Points 50

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

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

first() émet 1, complète, mais ne se désinscrit pas.

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

UPD : Ceci fait référence à RxJS 5.2.0. Il se peut que ce problème soit déjà résolu.

0 votes

Je ne pense pas que l'un ou l'autre se désabonne, voir jsbin.com/nuzulorota/1/edit?js,console .

12 votes

Oui, les deux opérateurs complètent l'abonnement, la différence se situe au niveau de la gestion des erreurs. Si cet observable n'émet pas de valeurs et que l'on essaie quand même de prendre la première valeur en utilisant le premier opérateur, une erreur sera levée. Si nous le remplaçons par l'opérateur take(1), même si la valeur n'est pas présente dans le flux au moment de la souscription, il n'y aura pas d'erreur.

9 votes

Pour clarifier : les deux se désabonnent. L'exemple de @weltschmerz était trop simplifié, il ne s'exécute pas tant qu'il ne peut pas se désabonner tout seul. Celui-ci est un peu plus développé : repl.it/repls/FrayedHugeAudacity

12voto

Artem Points 376

Il semble que, dans RxJS 5.2.0, la fonction .first() L'opérateur a un bogue ,

A cause de ce bug .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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X