take (1) vs first ()


137

Ho trovato alcune implementazioni di AuthGuards che usano take(1). Nel mio progetto, ho usato first().

Entrambi funzionano allo stesso modo?

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

Risposte:


198

Gli operatori first()e take(1)non sono gli stessi.

L' first()operatore accetta una predicatefunzione opzionale ed emette una errornotifica quando nessun valore corrisponde quando la fonte è stata completata.

Ad esempio, questo genererà un errore:

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

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

... oltre a questo:

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

Mentre questo corrisponderà al primo valore emesso:

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

D'altra parte take(1)prende solo il primo valore e lo completa. Nessuna ulteriore logica è coinvolta.

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

Quindi con sorgente vuota osservabile non emetterà alcun errore:

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

Gennaio 2019: aggiornato per RxJS 6


2
Proprio come nota, non l'ho detto first()e take()sono lo stesso in generale, che penso sia ovvio, solo quello first()e take(1)sono gli stessi. Non sono sicuro della tua risposta se pensi che ci sia ancora una differenza?
Günter Zöchbauer,

14
@ GünterZöchbauer In realtà, il loro comportamento è diverso. Se la fonte non emette nulla e viene completata, first()invia una notifica di errore mentre take(1)semplicemente non emette nulla.
Martin,

@martin, in alcuni casi take (1) non emetterà nulla significa dire che il debug del codice sarà più difficile?
Karuban,

7
@Karuban Dipende molto dal tuo caso d'uso. Se non si riceve alcun valore è imprevisto di quanto suggerirei di utilizzare first(). Se è uno stato di applicazione valido andrei con take(1).
Martin,

2
Questo è simile al .First()vs di .NET .FirstOrDefault()(e vieni a pensarci anche .Take(1)in quanto First richiede qualcosa nella raccolta e dà un errore per una raccolta vuota - ed entrambi FirstOrDefault()e .Take(1)permettono alla raccolta di essere vuota e restituire nulle svuotare la raccolta rispettivamente.
Simon_Weaver

45

Suggerimento: utilizzare solo first()se:

  • Consideri zero elementi emessi una condizione di errore (ad es. Completamento prima dell'emissione) E se c'è una probabilità di errore superiore allo 0%, la gestisci con grazia
  • OPPURE Sai al 100% che la fonte osservabile emetterà 1+ oggetti (quindi non puoi mai lanciare) .

Se non vi sono emissioni zero e non la si sta gestendo esplicitamente (con catchError), allora l'errore verrà propagato, probabilmente causerà un problema imprevisto altrove e può essere abbastanza difficile da rintracciare, soprattutto se proviene da un utente finale.

Sei più sicuro usando take(1)per la maggior parte a condizione che:

  • Stai bene take(1)non emettere nulla se la fonte viene completata senza emissione.
  • Non è necessario utilizzare un predicato inline (ad es. first(x => x > 10))

Nota: è possibile utilizzare un predicato con take(1)come questo: .pipe( filter(x => x > 10), take(1) ). Non vi è alcun errore in questo caso se nulla è mai maggiore di 10.

Che dire single()

Se vuoi essere ancora più severo e non consentire due emissioni, puoi utilizzare single()quali errori se ci sono zero o 2+ emissioni . Ancora una volta avresti bisogno di gestire gli errori in quel caso.

Suggerimento: a Singlevolte può essere utile se vuoi assicurarti che la tua catena osservabile non stia facendo un lavoro extra come chiamare due volte un servizio http ed emettere due osservabili. L'aggiunta singlealla fine del tubo ti farà sapere se hai fatto un tale errore. Lo sto usando in un 'task runner' in cui passi un'attività osservabile che dovrebbe emettere un solo valore, quindi passo la risposta single(), catchError()per garantire un buon comportamento.


Perché non usare sempre first()invece di take(1)?

aka. Come può first potenzialmente causare più errori?

Se hai un osservabile che prende qualcosa da un servizio e poi lo convoglia, first()dovresti andare bene per la maggior parte del tempo. Ma se qualcuno arriva per disabilitare il servizio per qualsiasi motivo - e lo modifica per emettere of(null)o NEVERqualsiasi first()operatore a valle inizierebbe a lanciare errori.

Ora mi rendo conto che potrebbe essere esattamente quello che vuoi, quindi perché questo è solo un suggerimento. L'operatore firstmi ha fatto appello perché sembrava un po 'meno "goffo" rispetto a take(1)ma devi fare attenzione a gestire gli errori se c'è mai la possibilità che la fonte non emetta. Dipenderà interamente da quello che stai facendo però.


Se hai un valore predefinito (costante):

Considera anche .pipe(defaultIfEmpty(42), first())se hai un valore predefinito che dovrebbe essere usato se non viene emesso nulla. Questo ovviamente non genererebbe un errore perché firstriceverebbe sempre un valore.

Si noti che defaultIfEmptyviene attivato solo se il flusso è vuoto, non se il valore di ciò che viene emesso è null.


Essere consapevoli del fatto che singleha più differenze first. 1. Emetterà solo il valore on complete. Ciò significa che se l'osservabile emette un valore ma non viene mai completato, single non emetterà mai un valore. 2. Per qualche motivo se si passa una funzione filtro a singlecui non corrisponde nulla emetterà un undefinedvalore se la sequenza originale non è vuota, il che non è il caso first.
Marinos il

28

Qui ci sono tre osservabili A, Be Ccon i diagrammi di marmo per esplorare la differenza tra first, takee singlegli operatori:

confronto prima vs take vs singolo operatore

* Legenda : errore di
--o-- valore
----!
----| completamento

Gioca con esso su https://thinkrx.io/rxjs/first-vs-take-vs-single/ .

Avendo già tutte le risposte, volevo aggiungere una spiegazione più visiva

Spero che aiuti qualcuno


12

C'è una differenza davvero importante che non è menzionata da nessuna parte.

take (1) emette 1, completa, annulla l'iscrizione

first () emette 1, completa, ma non annulla l'iscrizione.

Significa che il tuo osservabile a monte sarà ancora caldo dopo first () che probabilmente non è un comportamento previsto.

UPD: si riferisce a RxJS 5.2.0. Questo problema potrebbe essere già stato risolto.


Non credo che neanche uno si annulli , vedere jsbin.com/nuzulorota/1/edit?js,console .
weltschmerz,

10
Sì, entrambi gli operatori completano l'abbonamento, la differenza si verifica nella gestione degli errori. Se tale osservabile non emette valori e prova comunque a prendere il primo valore utilizzando il primo operatore, verrà generato un errore. Se lo sostituiamo con l'operatore take (1) anche se il valore non è presente nello stream quando si verifica la sottoscrizione, non viene generato un errore.
noelyahan,

7
Per chiarire: entrambi annullare l'iscrizione. L'esempio di @weltschmerz era troppo semplificato, non funziona fino a quando non può annullare l'iscrizione da solo. Questo è un po 'più espanso: repl.it/repls/FrayedHugeAudacity
Stephan LV

10

Sembra che in RxJS 5.2.0 l' .first()operatore abbia un bug ,

A causa di quel bug .take(1)e .first()può comportarsi in modo abbastanza diverso se li stai usando con switchMap:

Con take(1)te otterrai il comportamento come previsto:

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

Ma con .first()te otterrai un comportamento sbagliato:

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

Ecco un link per codepen

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.