take(1) vs first()

Ik heb een paar implementaties gevonden van AuthGuard‘s die take(1)gebruiken. In mijn project heb ik first()gebruikt.

Werken beide op dezelfde manier?

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

Antwoord 1, autoriteit 100%

Operators first()en take(1)zijn niet hetzelfde.

De operator first()neemt een optionele functie predicateen geeft een error-melding wanneer er geen waarde overeenkwam toen de bron klaar was.

p>

Dit geeft bijvoorbeeld een foutmelding:

import { EMPTY, range } from 'rxjs';
import { first, take } from 'rxjs/operators';
EMPTY.pipe(
  first(),
).subscribe(console.log, err => console.log('Error', err));

… evenals dit:

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

Hoewel dit overeenkomt met de eerste verzonden waarde:

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

Aan de andere kant neemt take(1)gewoon de eerste waarde en voltooit. Er is geen verdere logica bij betrokken.

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

Dan met lege bron Waarneembaar zal het geen enkele fout uitzenden:

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

Jan 2019: bijgewerkt voor RxJS 6


Antwoord 2, autoriteit 27%

Tip: gebruik first()alleen als:

  • U beschouwt nul verzonden items als een foutconditie (bijv. voltooien voordat ze worden verzonden)EN als er een kans van meer dan 0% is op fouten, handelt u het netjes af
  • OF Je weet 100% dat de waarneembare bron 1+ items zal uitzenden (dus nooit kan gooien).

Als er geen emissies zijn en je handelt er niet expliciet mee (met catchError), dan wordt die fout gepropageerd, kan ergens anders een onverwacht probleem veroorzaken en kan het behoorlijk lastig zijn om op te sporen – vooral als het van een eindgebruiker komt.

U bent veiligeraf als u take(1)grotendeels gebruikt, op voorwaarde dat:

  • U vindt het goed dat take(1)niets uitzendt als de bron zonder emissie wordt uitgezonden.
  • U hoeft geen inline predikaat te gebruiken (bijv. first(x => x > 10))

Opmerking: u kunteen predikaat gebruiken met take(1)als volgt: .pipe( filter(x => x > 10), take(1) ). Er is hier geen fout mee als niets ooit groter is dan 10.

Hoe zit het met single()

Als u nog strenger wilt zijn en twee emissies niet wilt toestaan, kunt u single()gebruiken die fouten maakt als er nul of 2+ emissieszijn. Ook in dat geval zou je fouten moeten afhandelen.

Tip: singlekan af en toe handig zijn als u ervoor wilt zorgen dat uw waarneembare keten geen extra werk doet, zoals twee keer een http-service aanroepen en twee waarneembare waarden uitzenden. Door singletoe te voegen aan het einde van de pipe, weet je of je een dergelijke fout hebt gemaakt. Ik gebruik het in een ‘task runner’ waar je een waarneembare taak doorgeeft die maar één waarde zou moeten uitstralen, dus ik geef het antwoord door via single(), catchError()om goed gedrag te garanderen.


Waarom niet altijd first()gebruiken in plaats van take(1)?

ook bekend. Hoe kan firstmogelijkmeer fouten veroorzaken?

Als je een waarneembaar object hebt dat iets van een service neemt en het vervolgens door first()stuurt, zou het meestal goed moeten komen. Maar als iemand langskomt om de service om welke reden dan ook uit te schakelen – en deze wijzigt om of(null)of NEVERuit te zenden, dan zal elke downstream first()operators zouden fouten gaan maken.

Nu realiseer ik me dat dit precieskan zijn wat je wilt – vandaar dat dit slechts een tip is. De operator firstsprak me aan omdat het iets minder ‘onhandig’ klonk dan take(1)maar je moet voorzichtig zijn met het afhandelen van fouten als er ooit een kans is dat de bron niet uitstoten. Zal echter volledig afhangen van wat je doet.


Als je een standaardwaarde hebt (constant):

Overweeg ook .pipe(defaultIfEmpty(42), first())als u een standaardwaarde heeft die moet worden gebruikt als er niets wordt verzonden. Dit zou natuurlijk geen fout opleveren omdat firstaltijd een waarde zou krijgen.

Houd er rekening mee dat defaultIfEmptyalleen wordt geactiveerd als de stream leeg is, niet als de waarde van wat wordt uitgezonden nullis.


Antwoord 3, autoriteit 19%

Hier zijn drie Observables A, Ben Cmet marmeren diagrammen om het verschil tussen firstte onderzoeken , takeen singleoperators:

first vs take vs single operators vergelijking

* Legende:
--o--waarde
----!fout
----|voltooiing

Speel ermee op https://thinkrx.io/ rxjs/first-vs-take-vs-single/.

Ik heb al alle antwoorden en wilde een meer visuele uitleg toevoegen

Ik hoop dat het iemand helpt


Antwoord 4, autoriteit 7%

Er is één heel belangrijk verschil dat nergens wordt vermeld.

take(1) zendt 1, voltooit, schrijft zich uit

first() zendt 1, voltooit, maar meldt zich niet af.

Het betekent dat je upstream-waarneembare nog steeds hot zal zijn na eerste(), wat waarschijnlijk niet verwacht gedrag is.

UPD: Dit verwijst naar RxJS 5.2.0. Dit probleem is mogelijk al opgelost.


Antwoord 5, autoriteit 4%

Het lijkt erop dat in RxJS 5.2.0 de operator .first()een bug,

Vanwege die bug kunnen .take(1)en .first()zich heel anders gedragen als je ze gebruikt met switchMap:

Met take(1)krijg je gedrag zoals verwacht:

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

Maar met .first()krijg je verkeerd gedrag:

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 is een link naar codepen


Antwoord 6, autoriteit 2%

Het blijkt dat er een heel belangrijk onderscheid is tussen de twee methoden: first()zal een foutmelding geven als de stream is voltooid voordat een waarde is verzonden. Of, als je een predikaat (i.e. first(value => value === 'foo'))hebt opgegeven, zal het een foutmelding geven als de stream is voltooid vóór een waarde die het predikaat passeert wordt uitgezonden.

take(1)daarentegen gaat graag door als er nooit een waarde uit de stream wordt uitgezonden. Hier is een eenvoudig voorbeeld:

const subject$ = new Subject();
// logs "no elements in sequence" when the subject completes
subject$.first().subscribe(null, (err) => console.log(err.message));
// never does anything
subject$.take(1).subscribe(console.log);
subject$.complete();

Nog een voorbeeld, waarbij een predikaat wordt gebruikt:

const observable$ = of(1, 2, 3);
// logs "no elements in sequence" when the observable completes
observable$
 .first((value) => value > 5)
 .subscribe(null, (err) => console.log(err.message));
// the above can also be written like this, and will never do
// anything because the filter predicate will never return true
observable$
 .filter((value) => value > 5);
 .take(1)
 .subscribe(console.log);

Als nieuwkomer bij RxJS was dit gedrag erg verwarrend voor mij, hoewel het mijn eigen schuld was omdat ik een aantal onjuiste veronderstellingen maakte. Als ik de moeite had genomen om de documenten te controleren, had ik gezien dat het gedrag duidelijk gedocumenteerd:

Geeft een foutmelding als defaultValueniet is opgegeven en er geen overeenkomend element wordt gevonden.

De reden dat ik dit zo vaak ben tegengekomen, is een vrij algemeen Angular 2-patroon waarbij waarneembare objecten handmatig worden opgeschoond tijdens de OnDestroylevenscyclushook:

class MyComponent implements OnInit, OnDestroy {
  private stream$: Subject = someDelayedStream();
  private destroy$ = new Subject();
  ngOnInit() {
    this.stream$
      .takeUntil(this.destroy$)
      .first()
      .subscribe(doSomething);
  }
  ngOnDestroy() {
    this.destroy$.next(true);
  }
}

De code ziet er op het eerste gezicht ongevaarlijk uit, maar er ontstaan ​​problemen wanneer het onderdeel wordt vernietigd voordat stream$een waarde kan afgeven. Omdat ik first()gebruik, wordt er een fout gegenereerd wanneer het onderdeel wordt vernietigd. Ik abonneer me meestal alleen op een stream om een ​​waarde te krijgen die binnen de component moet worden gebruikt, dus het kan me niet schelen of de component wordt vernietigd voordat de stream wordt uitgezonden. Daarom ben ik take(1)gaan gebruiken op bijna alle plaatsen waar ik eerder first()zou hebben gebruikt.

filter(fn).take(1)is wat uitgebreider dan first(fn), maar in de meeste gevallen geef ik de voorkeur aan wat meer breedsprakigheid boven het afhandelen van fouten die uiteindelijk geen invloed hebben op de toepassing.

Ook belangrijk om op te merken: hetzelfde geldt voor last()en takeLast(1).

referentie

Other episodes