take(1) vs first()
Asked Answered
Z

6

250

I found a few implementation of AuthGuards that use take(1). In my project, I used first().

Do both work the same way?

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)
    }
}
Zurek answered 20/2, 2017 at 13:34 Comment(0)
K
334

Operators first() and take(1) aren't the same.

The first() operator takes an optional predicate function and emits an error notification when no value matched when the source completed.

For example this will emit an error:

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

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

... as well as this:

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

While this will match the first value emitted:

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

On the other hand take(1) just takes the first value and completes. No further logic is involved.

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

Then with empty source Observable it won't emit any error:

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

Jan 2019: Updated for RxJS 6

Kenway answered 20/2, 2017 at 13:44 Comment(5)
Just as note, I didn't say that first() and take() are the same in general, which I think is obvious, only that first() and take(1) are the same. I'm not sure from your answer if you think there is still a difference?Accompanyist
@GünterZöchbauer Actually, their behavior is different. If the source doesn't emit anything and completes then first() send error notification while take(1) simply won't emit anything.Kenway
@martin, at some cases take(1) won't emit anything means to say debugging the code will be harder?Zurek
@Karuban This really depends on your usecase. If not receiving any value is unexpected than I'd suggest to use first(). If it's a valid application state I'd go with take(1).Kenway
This is similar to .NET's .First() vs .FirstOrDefault() (and come to think of it also .Take(1) in that First requires something in the collection and gives an error for an empty collection - and both FirstOrDefault() and .Take(1) allow the collection to be empty and return null and empty collection respectively.Tharpe
T
86

Tip: Only use first() if:

  • You consider zero items emitted to be an error condition (eg. completing before emitting) AND if there’s a greater than 0% chance of error you handling it gracefully
  • OR You know 100% that the source observable will emit 1+ items (so can never throw).

If there are zero emissions and you are not explicitly handling it (with catchError) then that error will get propagated up, possibly cause an unexpected problem somewhere else and can be quite tricky to track down - especially if it's coming from an end user.

You're safer off using take(1) for the most part provided that:

  • You're OK with take(1) not emitting anything if the source completes without an emission.
  • You don't need to use an inline predicate (eg. first(x => x > 10) )

Note: You can use a predicate with take(1) like this: .pipe( filter(x => x > 10), take(1) ). There is no error with this if nothing is ever greater than 10.

What about single()

If you want to be even stricter, and disallow two emissions you can use single() which errors if there are zero or 2+ emissions. Again you'd need to handle errors in that case.

Tip: Single can occasionally be useful if you want to ensure your observable chain isn't doing extra work like calling an http service twice and emitting two observables. Adding single to the end of the pipe will let you know if you made such a mistake. I'm using it in a 'task runner' where you pass in a task observable that should only emit one value, so I pass the response through single(), catchError() to guarantee good behavior.


Why not always use first() instead of take(1) ?

aka. How can first potentially cause more errors?

If you have an observable that takes something from a service and then pipes it through first() you should be fine most of the time. But if someone comes along to disable the service for whatever reason - and changes it to emit of(null) or NEVER then any downstream first() operators would start throwing errors.

Now I realize that might be exactly what you want - hence why this is just a tip. The operator first appealed to me because it sounded slightly less 'clumsy' than take(1) but you need to be careful about handling errors if there's ever a chance of the source not emitting. Will entirely depend on what you're doing though.


If you have a default value (constant):

Consider also .pipe(defaultIfEmpty(42), first()) if you have a default value that should be used if nothing is emitted. This would of course not raise an error because first would always receive a value.

Note that defaultIfEmpty is only triggered if the stream is empty, not if the value of what is emitted is null.

Tharpe answered 16/1, 2019 at 3:11 Comment(2)
Be aware that single has more differences to first. 1. It will only emit the value on complete. This means that if the observable emits a value but never completes then single will never emit a value. 2. For some reason if you pass a filter function to single which does not match anything it will emit an undefined value if original sequence is not empty, which is not the case with first.Satiety
Regarding 2. It was a bug that is now fixed.Satiety
B
61

Here are three Observables A, B, and C with marble diagrams to explore the difference between first, take, and single operators:

first vs take vs single operators comparison

* Legend:
--o-- value
----! error
----| completion

Play with it at https://thinkrx.io/rxjs/first-vs-take-vs-single/ .

Already having all the answers, I wanted to add a more visual explanation

Hope it helps someone

Bureaucratize answered 21/5, 2019 at 10:47 Comment(2)
does first() complete after receiving the first value?Pisarik
@FernandoGabrieli, yep! It completes immediately after emitting the first value. On the viz the | behind the (0) indicates that. More details at thinkrx.io/rxjs/firstBureaucratize
M
25

There's one really important difference which is not mentioned anywhere.

take(1) emits 1, completes, unsubscribes

first() emits 1, completes, but doesn't unsubscribe.

It means that your upstream observable will still be hot after first() which is probably not expected behavior.

UPD: This referes to RxJS 5.2.0. This issue might be already fixed.

Macrophage answered 10/3, 2017 at 14:40 Comment(3)
I don't think either one unsubscribes, see jsbin.com/nuzulorota/1/edit?js,console.Steadfast
Yes, both operators complete the subscription, the difference happens in the error handling. If that observable does not emit values and still try to take the first value using the first operator it will throw an error. If we replace it with take(1) operator even though value is not there in the stream when the subscription happens it does not throw an error.Bellybutton
To clarify: both do unsubscribe. The example from @weltschmerz was too simplified, it does not run until it could unsubscribe by itself. This one is a little more expanded: repl.it/repls/FrayedHugeAudacityShriner
V
13

It seems that in RxJS 5.2.0 the .first() operator has a bug,

Because of that bug .take(1) and .first() can behave quite different if you are using them with switchMap:

With take(1) you will get behavior as expected:

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

But with .first() you will get wrong behavior:

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

Here's a link to codepen

Valley answered 10/3, 2017 at 16:4 Comment(0)
R
11

It turns out there's a very important distinction between the two methods: first() will emit an error if the stream completes before a value is emitted. Or, if you've provided a predicate (i.e. first(value => value === 'foo')), it will emit an error if the stream completes before a value that passes the predicate is emitted.

take(1), on the other hand, will happily carry on if a value is never emitted from the stream. Here's a simple example:

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();

Another example, using a predicate:

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

As a newcomer to RxJS, this behavior was very confusing to me, although it was my own fault because I made some incorrect assumptions. If I had bothered to check the docs, I would have seen that the behavior is clearly documented:

Throws an error if defaultValue was not provided and a matching element is not found.

The reason I've run into this so frequently is a fairly common Angular 2 pattern where observables are cleaned up manually during the OnDestroy lifecycle hook:

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

The code looks harmless at first, but problems arise when the component in destroyed before stream$ can emit a value. Because I'm using first(), an error is thrown when the component is destroyed. I'm usually only subscribing to a stream to get a value that is to be used within the component, so I don't care if the component gets destroyed before the stream emits. Because of this, I've started using take(1) in almost all places where I would have previously used first().

filter(fn).take(1) is a bit more verbose than first(fn), but in most cases I prefer a little more verbosity over handling errors that ultimately have no impact on the application.

Also important to note: The same applies for last() and takeLast(1).

reference

Riess answered 23/8, 2020 at 20:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.