tomar (1) vs primero ()

137

Encontré algunas implementaciones de AuthGuards que usan take(1). En mi proyecto, solía first().

¿Ambos funcionan de la misma manera?

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
fuente

Respuestas:

198

Operadores first()y take(1)no son lo mismo.

El first()operador toma una predicatefunción opcional y emite una errornotificación cuando ningún valor coincide cuando se completa la fuente.

Por ejemplo, esto emitirá un error:

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

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

... tan bien como esto:

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

Si bien esto coincidirá con el primer valor emitido:

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

Por otro lado take(1)solo toma el primer valor y completa. No hay más lógica involucrada.

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

Luego, con la fuente vacía Observable, no emitirá ningún error:

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

Enero de 2019: actualizado para RxJS 6

martín
fuente
2
Solo como nota, no dije eso first()y take()son lo mismo en general, lo cual creo que es obvio, solo eso first()y take(1)son lo mismo. No estoy seguro de su respuesta si cree que todavía hay una diferencia.
Günter Zöchbauer
14
@ GünterZöchbauer En realidad, su comportamiento es diferente. Si la fuente no emite nada y se completa, first()envíe una notificación de error mientras que take(1)simplemente no emitirá nada.
Martin
@martin, en algunos casos, take (1) no emitirá nada significa decir que depurar el código será más difícil.
Karuban
77
@Karuban Esto realmente depende de su caso de uso. Si no recibe ningún valor es inesperado, sugeriría usar first(). Si es un estado de aplicación válido, iría con take(1).
Martin
2
Esto es similar a .NET's .First()vs .FirstOrDefault()(y ahora que lo pienso también, .Take(1)Primero requiere algo en la colección y da un error para una colección vacía, y ambos, FirstOrDefault()y .Take(1)permite que la colección esté vacía y devuelva nully la colección vacía, respectivamente.
Simon_Weaver
45

Consejo: Úselo solo first()si:

  • Considera que cero elementos emitidos son una condición de error (por ejemplo, completar antes de emitir) Y si hay más de un 0% de posibilidades de error, lo maneja con gracia
  • O Usted sabe que el 100% observables fuente emitirá 1+ artículos (por lo que nunca puede lanzar) .

Si hay cero emisiones y no lo está manejando explícitamente (con catchError), entonces ese error se propagará, posiblemente causará un problema inesperado en otro lugar y puede ser bastante difícil de rastrear, especialmente si proviene de un usuario final.

Estás más seguro usando take(1)la mayor parte siempre que:

  • Estás de acuerdo con take(1)no emitir nada si la fuente se completa sin una emisión.
  • No necesita usar un predicado en línea (p. Ej. first(x => x > 10))

Nota: Se puede utilizar un predicado con take(1)este aspecto: .pipe( filter(x => x > 10), take(1) ). No hay ningún error con esto si nada es mayor que 10.

Qué pasa single()

Si desea ser aún más estricto y no permitir dos emisiones, puede usar single()qué errores si hay cero o más de 2 emisiones . Nuevamente, necesitaría manejar los errores en ese caso.

Consejo: Singleocasionalmente puede ser útil si desea asegurarse de que su cadena observable no esté haciendo un trabajo adicional, como llamar a un servicio http dos veces y emitir dos observables. Agregar singleal final de la tubería le permitirá saber si cometió tal error. Lo estoy usando en un 'corredor de tareas' donde pasas una tarea observable que solo debería emitir un valor, así que paso la respuesta single(), catchError()para garantizar un buen comportamiento.


¿Por qué no usar siempre en first()lugar de take(1)?

aka. ¿Cómo puede first potencialmente causar más errores?

Si tiene un observable que toma algo de un servicio y luego lo canaliza, first()debería estar bien la mayor parte del tiempo. Pero si alguien viene a deshabilitar el servicio por cualquier razón, y lo cambia para emitir of(null)o NEVERcualquier first()operador posterior comenzaría a arrojar errores.

Ahora me doy cuenta de que podría ser exactamente lo que quieres, de ahí que esto sea solo un consejo. El operador firstme atrajo porque sonaba un poco menos `` torpe '' que, take(1)pero debes tener cuidado con el manejo de errores si alguna vez existe la posibilidad de que la fuente no se emita. Sin embargo, dependerá completamente de lo que estés haciendo.


Si tiene un valor predeterminado (constante):

Considere también .pipe(defaultIfEmpty(42), first())si tiene un valor predeterminado que debe usarse si no se emite nada. Por supuesto, esto no generaría un error porque firstsiempre recibiría un valor.

Tenga en cuenta que defaultIfEmptysolo se activa si la secuencia está vacía, no si el valor de lo que se emite es null.

Simon_Weaver
fuente
Tenga en cuenta que singletiene más diferencias con first. 1. Solo emitirá el valor en complete. Esto significa que si el observable emite un valor pero nunca se completa, single nunca emitirá un valor. 2. Por alguna razón, si pasa una función de filtro singleque no coincide con nada, emitirá un undefinedvalor si la secuencia original no está vacía, que no es el caso first.
Marinos un
28

Aquí hay tres observables A, By Ccon los diagramas de mármol para explorar la diferencia entre first, takey singlelos operadores:

Comparación de primero vs toma vs operadores individuales

* Leyenda :
--o-- valor de
----! error
----| completado

Juega con él en https://thinkrx.io/rxjs/first-vs-take-vs-single/ .

Ya teniendo todas las respuestas, quería agregar una explicación más visual.

Espero que ayude a alguien

kos
fuente
12

Hay una diferencia realmente importante que no se menciona en ninguna parte.

take (1) emite 1, completa, cancela la suscripción

first () emite 1, completa, pero no se da de baja.

Significa que su observable aguas arriba todavía estará caliente después de first (), lo que probablemente no sea un comportamiento esperado.

UPD: Esto se refiere a RxJS 5.2.0. Este problema podría estar ya solucionado.

norekhov
fuente
No creo que ninguno se dé de baja, consulte jsbin.com/nuzulorota/1/edit?js,console .
weltschmerz
10
Sí, ambos operadores completan la suscripción, la diferencia ocurre en el manejo de errores. Si ese observable no emite valores y aún intenta tomar el primer valor utilizando el primer operador, arrojará un error. Si lo reemplazamos con el operador take (1) a pesar de que el valor no está en la secuencia cuando ocurre la suscripción, no arroja un error.
noelyahan
77
Para aclarar: ambos se dan de baja. El ejemplo de @weltschmerz fue demasiado simplificado, no se ejecuta hasta que puede darse de baja por sí mismo. Este es un poco más expandido: repl.it/repls/FrayedHugeAudacity
Stephan LV
10

Parece que en RxJS 5.2.0 el .first()operador tiene un error ,

Debido a ese error .take(1)y .first()puede comportarse de manera bastante diferente si los está usando con switchMap:

Con take(1)usted obtendrá un comportamiento como se esperaba:

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

Pero con .first()usted obtendrá un comportamiento incorrecto:

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

Aquí hay un enlace a codepen

Artem
fuente