prendre (1) vs premier ()

137

J'ai trouvé quelques implémentations de AuthGuards qui utilisent take(1). Dans mon projet, j'ai utilisé first().

Les deux fonctionnent-ils de la même manière?

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)
    }
}
Karuban
la source

Réponses:

198

Opérateurs first()ettake(1) ne sont pas les mêmes.

L' first()opérateur prend une predicatefonction optionnelle et émet unerror notification lorsqu'aucune valeur ne correspond lorsque la source est terminée.

Par exemple, cela é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 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));

D'autre part take(1)prend juste la première valeur et complète. 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, il n'émettra aucune erreur:

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

Janvier 2019: mise à jour pour RxJS 6

Martin
la source
2
Juste comme note, je n'ai pas dit cela first()et take()sont les mêmes en général, ce qui je pense est évident, seulement cela first()et take(1)sont les mêmes. Je ne suis pas sûr d'après votre réponse si vous pensez qu'il y a encore une différence?
Günter Zöchbauer
14
@ GünterZöchbauer En fait, leur comportement est différent. Si la source n'émet rien et se termine, first()envoyez une notification d'erreur alors take(1)qu'elle n'émettra tout simplement rien.
martin
@martin, dans certains cas, take (1) n'émettra rien signifie que le débogage du code sera plus difficile?
Karuban
7
@Karuban Cela dépend vraiment de votre cas d'utilisation. Si ne pas recevoir de valeur est inattendu que je suggérerais d'utiliser first(). Si c'est un état d'application valide, j'irais avec take(1).
martin le
2
Ceci est similaire au .First()vs de .NET .FirstOrDefault()(et en vient à y penser aussi .Take(1)en ce que First nécessite quelque chose dans la collection et donne une erreur pour une collection vide - et les deux FirstOrDefault()et .Take(1)autorise la collection à être vide et retourne nullet une collection vide respectivement.
Simon_Weaver
45

Astuce: à n'utiliser que first()si:

  • Vous considérez zéro élément émis comme une condition d'erreur (par exemple, terminer avant l'émission) ET s'il y a plus de 0% de chances d'erreur, vous le gérez correctement
  • OU Vous savez à 100% que l'observable source émettra 1+ éléments (donc ne peut jamais lancer) .

S'il n'y a aucune émission et que vous ne la gérez pas explicitement (avec catchError), cette erreur se propagera, 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 d'utiliser take(1)la plupart du temps à condition que:

  • Vous êtes d'accord pour take(1)ne rien émettre 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 take(1)comme ceci: .pipe( filter(x => x > 10), take(1) ). Il n'y a pas d'erreur avec ceci 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 2+ émissions . Encore une fois, vous devrez gérer les erreurs dans ce cas.

Astuce: Singlepeut parfois être utile si vous voulez vous assurer que votre chaîne observable ne fait pas de 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 fait une telle erreur. Je l'utilise dans un 'task runner' où vous passez une tâche observable qui ne devrait émettre qu'une seule valeur, donc je transmets la réponse single(), catchError()pour garantir un bon comportement.


Pourquoi ne pas toujours utiliser à la first()place de take(1)?

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

Si vous avez un observable qui prend quelque chose d'un service et qui le transite ensuite, first()vous devriez être en ordre la plupart du temps. Mais si quelqu'un vient désactiver le service pour une raison quelconque - et le change pour émettre of(null)ou NEVERalors tout first()opérateur en aval commencerait à lancer des erreurs.

Maintenant, je réalise que c'est peut-être exactement ce que vous voulez - d'où la raison pour laquelle ce n'est qu'un conseil. L'opérateur firstm'a séduit parce que cela semblait un peu moins `` maladroit '' que, take(1)mais vous devez faire attention à la gestion des erreurs s'il y a un risque 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. Cela ne soulèverait bien sûr pas une erreur car firstrecevrait toujours une valeur.

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

Simon_Weaver
la source
Sachez qu'il y singlea 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 transmettez une fonction de filtre à singlelaquelle ne correspond rien, elle émettra une undefinedvaleur si la séquence d'origine n'est pas vide, ce qui n'est pas le cas avec first.
Marinos An
28

Voici trois Observables A, Bet Cavec des schémas de marbre pour explorer la différence entre first, takeet les singleopérateurs:

Comparaison entre les opérateurs first vs take vs single

* Légende : achèvement de l' erreur de
--o-- valeur
----!
----|

Jouez avec sur 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

kos
la source
12

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

take (1) émet 1, termine, 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 first (), ce qui n'est probablement pas un comportement attendu.

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

Norekhov
la source
Je ne pense pas que l'un ou l'autre se désabonne, voir jsbin.com/nuzulorota/1/edit?js,console .
weltschmerz
10
Oui, les deux opérateurs complètent l'abonnement, la différence se produit dans la gestion des erreurs. Si cette observable n'émet pas de valeurs et essaie toujours de prendre la première valeur en utilisant le premier opérateur, elle lèvera une erreur. Si nous le remplaçons par l'opérateur take (1) même si la valeur n'est pas présente dans le flux lorsque l'abonnement se produit, cela ne génère pas d'erreur.
noelyahan
7
Pour clarifier: les deux se désabonnent. L'exemple de @weltschmerz était trop simplifié, il ne s'exécute pas tant qu'il n'a pas pu se désabonner par lui-même. Celui-ci est un peu plus développé: repl.it/repls/FrayedHugeAudacity
Stephan LV
10

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

À cause de ce bug .take(1)et .first()peuvent se comporter très différemment si vous les utilisez avec switchMap:

Avec take(1)vous obtiendrez un comportement comme prévu:

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, 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

Artem
la source