nimm (1) vs first ()

137

Ich habe einige Implementierungen von AuthGuards gefunden, die diese verwenden take(1). In meinem Projekt habe ich verwendet first().

Arbeiten beide gleich?

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
quelle

Antworten:

197

Operatoren first()und take(1)sind nicht gleich.

Der first()Bediener übernimmt eine optionale predicateFunktion und gibt eine errorBenachrichtigung aus, wenn nach Abschluss der Quelle kein Wert mehr übereinstimmt.

Dies gibt beispielsweise einen Fehler aus:

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

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

... so gut wie das:

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

Dies entspricht zwar dem ersten ausgegebenen Wert:

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

Auf der anderen Seite take(1)nimmt nur der erste Wert und vervollständigt. Es ist keine weitere Logik beteiligt.

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

Bei leerer Quelle Observable wird dann kein Fehler ausgegeben:

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

Jan 2019: Aktualisiert für RxJS 6

Martin
quelle
1
Nur als Anmerkung, ich habe das nicht gesagt first()und take()bin im Allgemeinen gleich, was ich für offensichtlich halte, nur das first()und take(1)sind gleich. Ich bin mir anhand Ihrer Antwort nicht sicher, ob Sie glauben, dass es noch einen Unterschied gibt?
Günter Zöchbauer
14
@ GünterZöchbauer Eigentlich ist ihr Verhalten anders. Wenn die Quelle nichts aussendet und abgeschlossen ist, first()senden Sie eine Fehlerbenachrichtigung, während Sie take(1)einfach nichts ausgeben.
Martin
@martin, in einigen Fällen wird take (1) nichts bedeuten, um zu sagen, dass das Debuggen des Codes schwieriger sein wird?
Karuban
7
@ Karuban Das hängt wirklich von Ihrem Anwendungsfall ab. Wenn es unerwartet ist, keinen Wert zu erhalten, würde ich empfehlen, ihn zu verwenden first(). Wenn es ein gültiger Anwendungsstatus ist, würde ich mit gehen take(1).
Martin
2
Dies ähnelt .NETs .First()vs .FirstOrDefault()(und denken Sie auch daran, .Take(1)dass First etwas in der Sammlung benötigt und einen Fehler für eine leere Sammlung FirstOrDefault().Take(1)null
ausgibt
43

Tipp: Nur verwenden, first()wenn:

  • Sie betrachten null ausgegebene Elemente als Fehlerbedingung (z. B. Abschluss vor dem Senden) UND wenn die Fehlerwahrscheinlichkeit größer als 0% ist, behandeln Sie sie ordnungsgemäß
  • ODER Sie wissen zu 100%, dass die beobachtbare Quelle mehr als 1 Elemente ausgibt (kann also niemals werfen) .

Wenn es keine Emissionen gibt und Sie nicht explizit damit umgehen (mit catchError), wird sich dieser Fehler ausbreiten, möglicherweise ein unerwartetes Problem an einer anderen Stelle verursachen und es kann ziemlich schwierig sein, ihn aufzuspüren - insbesondere, wenn er von einem Endbenutzer stammt.

Sie sind größtenteils sicherer , take(1)vorausgesetzt, dass:

  • Sie können take(1)nichts aussenden, wenn die Quelle ohne Emission abgeschlossen ist.
  • Sie müssen kein Inline-Prädikat verwenden (z. B. first(x => x > 10))

Hinweis: Sie können ein Prädikat take(1)wie folgt verwenden : .pipe( filter(x => x > 10), take(1) ). Dies ist kein Fehler, wenn nichts größer als 10 ist.

Wie wäre es mit single()

Wenn Sie noch strenger sein und zwei Emissionen nicht zulassen möchten, können Sie single()welche Fehler verwenden, wenn es keine oder mehr Emissionen gibt . Auch in diesem Fall müssten Sie Fehler behandeln.

Tipp: SingleKann gelegentlich nützlich sein, wenn Sie sicherstellen möchten, dass Ihre beobachtbare Kette keine zusätzliche Arbeit leistet, z. B. zweimal einen http-Dienst aufruft und zwei beobachtbare Elemente ausgibt. Wenn Sie singleam Ende des Rohrs hinzufügen, werden Sie informiert, wenn Sie einen solchen Fehler gemacht haben. Ich verwende es in einem 'Task Runner', in dem Sie eine beobachtbare Aufgabe übergeben, die nur einen Wert ausgeben sollte. Daher leite ich die Antwort weiter single(), catchError(), um ein gutes Verhalten zu gewährleisten.


Warum nicht immer first()statt verwenden take(1)?

aka. Wie kann first möglicherweise mehr Fehler verursachen?

Wenn Sie ein Observable haben, das etwas von einem Service nimmt und es dann durchleitet, sollte es first()Ihnen die meiste Zeit gut gehen. Aber wenn jemand kommt , den Service für welchen Gründen auch immer zu deaktivieren - und ändert es emittieren of(null)oder NEVERdann alle nachgelagerten first()Betreiber beginnen würde , Fehler zu werfen.

Jetzt ist mir klar, dass dies genau das sein könnte , was Sie wollen - daher ist dies nur ein Tipp. Der Betreiber hat firstmich angesprochen, weil es etwas weniger "ungeschickt" klang als, take(1)aber Sie müssen vorsichtig mit Fehlern umgehen, wenn jemals die Möglichkeit besteht, dass die Quelle nicht emittiert. Wird ganz davon abhängen, was Sie tun.


Wenn Sie einen Standardwert (Konstante) haben:

Überlegen Sie auch, .pipe(defaultIfEmpty(42), first())ob Sie einen Standardwert haben, der verwendet werden soll, wenn nichts ausgegeben wird. Dies würde natürlich keinen Fehler auslösen, da firstimmer ein Wert erhalten würde.

Beachten Sie, dass dies defaultIfEmptynur ausgelöst wird, wenn der Stream leer ist, nicht wenn der Wert der ausgegebenen Daten ist null.

Simon_Weaver
quelle
Seien Sie sich bewusst, dass singlees mehr Unterschiede zu gibt first. 1. Es wird nur der Wert on ausgegeben complete. Dies bedeutet, dass wenn das Observable einen Wert ausgibt, aber niemals abgeschlossen wird, Single niemals einen Wert ausgibt. 2. Wenn Sie aus irgendeinem Grund eine Filterfunktion übergeben, singledie mit nichts übereinstimmt, wird ein undefinedWert ausgegeben , wenn die ursprüngliche Sequenz nicht leer ist, was bei nicht der Fall ist first.
Marinos An
28

Hier sind drei Observable A, Bund Cmit Marmor Diagramme , die den Unterschied zwischen zu erkunden first, takeund singleOperatoren:

Vergleich von first vs take vs single operator

* Legende :
--o-- Wert
----! Fehler
----| Abschluss

Spielen Sie damit unter https://thinkrx.io/rxjs/first-vs-take-vs-single/ .

Da ich bereits alle Antworten hatte, wollte ich eine visuellere Erklärung hinzufügen

Hoffe es hilft jemandem

kos
quelle
12

Es gibt einen wirklich wichtigen Unterschied, der nirgendwo erwähnt wird.

take (1) gibt 1 aus, schließt ab, meldet sich ab

first () gibt 1 aus, wird abgeschlossen, aber nicht abgemeldet.

Dies bedeutet, dass Ihr Upstream-Observable nach first () immer noch heiß ist, was wahrscheinlich nicht zu erwarten ist.

UPD: Dies bezieht sich auf RxJS 5.2.0. Dieses Problem ist möglicherweise bereits behoben.

Norekhov
quelle
Ich glaube nicht, dass sich einer abmeldet , siehe jsbin.com/nuzulorota/1/edit?js,console .
Weltschmerz
10
Ja, beide Betreiber schließen das Abonnement ab, der Unterschied liegt in der Fehlerbehandlung. Wenn dieses Observable keine Werte ausgibt und dennoch versucht, den ersten Wert mit dem ersten Operator zu übernehmen, wird ein Fehler ausgegeben. Wenn wir es durch den Operator take (1) ersetzen, obwohl der Wert zum Zeitpunkt des Abonnements nicht im Stream vorhanden ist, wird kein Fehler ausgegeben.
Noelyahan
7
Zur Verdeutlichung: Beide melden sich ab. Das Beispiel von @weltschmerz war zu vereinfacht, es läuft erst, wenn es sich von selbst abmelden konnte. Dieser ist etwas erweitert: repl.it/repls/FrayedHugeAudacity
Stephan LV
10

Es scheint, dass in RxJS 5.2.0 der .first()Operator einen Fehler hat ,

Aufgrund dieses Fehlers .take(1)und .first()kann sich ganz anders verhalten, wenn Sie sie verwenden mit switchMap:

Mit erhalten take(1)Sie Verhalten wie erwartet:

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

Aber mit .first()dir wirst du falsches Verhalten bekommen:

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

Hier ist ein Link zum Codepen

Artem
quelle