237

I need to create a subscription to an Observable that is immediately disposed of when it is first called.

Is there something like:

observable.subscribeOnce(func);

My use case, I am creating a subscription in an express route handler and the subscription is being called multiple times per request.

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Berkeley Martinez
  • 2,786
  • 2
  • 14
  • 18

7 Answers7

422

Not 100% certain about what you need, but if you only want to observe the first value, then use either first() or take(1):

observable.first().subscribe(func);

note: .take(1) and .first() both unsubscribe automatically when their condition is met

Update from RxJS 5.5+

From comment by Coderer.

import { first } from 'rxjs/operators'
    
observable
  .pipe(first())
  .subscribe(func);

Here's why

acdcjunior
  • 132,397
  • 37
  • 331
  • 304
Brandon
  • 38,310
  • 8
  • 82
  • 87
  • 46
    Does the subscription get automatically cleaned up after? – Berkeley Martinez Jan 20 '15 at 02:32
  • 57
    Yes it does. Take and first both unsubscribe when their condition is met. – Brandon Jan 20 '15 at 02:36
  • 22
    Why doesn't the documentation say that it automatically disposes of the subscription? – jzig Mar 15 '16 at 15:38
  • 23
    It's a general RxJS rule that subscriptions are disposed when an observable stream ends. That means any operator that "shortens" a stream (or transforms it into a different type of stream) will unsubscribe from the source stream when their operation completes. I'm not sure where or if this is stated in the documentation. – Brandon Mar 15 '16 at 15:41
  • 1
    @Brandon what about if the first value never gets returned, will it cause memory leakage? Is it best to also have an unmount function that will unsubscribe/dispose the observable? – Beastwood Oct 03 '16 at 17:59
  • @Beastwood yes if you subscribe when a component is mounted, then you want to be sure to unsubscribe when the component is unmounted to prevent resource leaks – Brandon Oct 04 '16 at 15:55
  • 1
    for some weird reason first() doesn't work for me, but take(1) does ... – peter Oct 08 '16 at 19:21
  • `first`? surely `last` or `pop` is what our brains want to hear – jenson-button-event Jun 20 '17 at 09:39
  • 50
    If anybody is coming to this in 2018, you actually want `observable.pipe(first()).subscribe(func)`, where `first` comes from `rxjs/operators`. – Coderer Sep 20 '18 at 15:15
  • @Coderer What is the difference? – Ismail Dec 05 '18 at 09:28
  • [here's why](https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md#why) – Ismail Dec 05 '18 at 12:40
  • 1
    [Here's why](https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/pipeable-operators.md#why) link as of May 2020. – Jehanlos May 08 '20 at 03:22
  • The link is still broken – cuongle Dec 11 '20 at 00:16
  • `observable.pipe(first())` didn't work for me (RxJS 5.5); `observable.first()` did work. – Liran H Dec 31 '20 at 16:08
37

RxJS has some of the best documentation I've ever come across. Following the bellow link will take you to an exceedingly helpful table mapping use cases to operators. For instance, under the "I want to take the first value" use case are three operators: first, firstOrDefault, and sample.

Note, if an observable sequence completes with no notifications, then the first operator notifies subscribers with an error while the firstOrDefault operator provides a default value to subscribers.

operator use case lookup

DetweilerRyan
  • 486
  • 4
  • 4
11

UPDATE(DEC/2021):

As toPromise() function has been deprecated in RxJS 7, new functions have been announced to be used instead of it. firstValueFrom and lastValueFrom.

firsValueFrom function resolves the first emitted value and directly unsubscribe from the resource. It rejects with an EmptyError when the Observable completes without emitting any value.

On the other hand, lastValueFrom function is, to a certain degree, same to toPromise() as it resolves the last value emitted when the observable completes. However, if the observable doesn't emit any value, it will reject with an EmptyError. Unlike toPromise() which resolve undefined when no value emits.

For more information, please check the docs.


Old Answer:

If you want to call an Observable only one time, it means you are not going to wait for a stream from it. So using toPromise() instead of subscribe() would be enough in your case as toPromise() doesn't need unsubscription.

M Fuat
  • 1,330
  • 3
  • 12
  • 24
2

To supplement @Brandon's answer, using first() or the like is also essential for updating a BehaviorSubject based on its Observable. For example (untested):

var subject = new BehaviorSubject({1:'apple',2:'banana'});
var observable = subject.asObservable();

observable
  .pipe(
    first(), // <-- Ensures no stack overflow
    flatMap(function(obj) {
      obj[3] = 'pear';
      return of(obj);
    })
  )
  .subscribe(function(obj) {
    subject.next(obj);
  });
Andy
  • 8,749
  • 5
  • 34
  • 59
2

Clean and Convenient Version

Expanding on M Fuat NUROĞLU's amazing answer on converting the observable to a promise, here's the very convenient version of it.

const value = await observable.toPromise();

console.log(value)

The beauty of this is that we can use that value like a normal variable without introducing another nested block!

This is especially handy when you need to get multiple values from multiple observables. Neat and clean.

const content = await contentObservable.toPromise();
const isAuthenticated = await isAuthenticatedObservable.toPromise();

if(isAuthenticated){
   service.foo(content)
}

Of course, you will have to make your containing function async if you are to go with this route. You can also just .then the promise if you don't want the containing function to be async

I'm not sure if there are tradeoffs with this approach, feel free to let me know in the comments so we are aware.

P.S. If you liked this answer, don't forget to upvote M Fuat NUROĞLU's Answer as well :)

Louie Almeda
  • 5,366
  • 30
  • 38
  • 4
    Please be aware that the Promise is not resolving until the underlying Observable has completed. So if `.toPromise()` is not working, check that your Observable is getting completed. For example a "take(1)" could help: `const value = await observable.pipe(take(1)).toPromise()` See this comment: https://github.com/ReactiveX/rxjs/issues/2536#issuecomment-306041463 – zauni Sep 09 '20 at 07:11
2

I had similar question.

Below was called even later on from different state changers. As I did not want to.

function foo() {
    // this was called many times which was not needed
    observable.subscribe(func);
    changeObservableState("new value");
}

I decided to try unsubscribe() after subscribe as below.

function foo() {
    // this was called ONE TIME
    observable.subscribe(func).unsubscribe();
    changeObservableState("new value");
}

subscribe(func).unsubscribe(); is like subscribeOnce(func).

I hope that helped you too.

MrHIDEn
  • 1,723
  • 1
  • 25
  • 23
1

observable.pipe(take(1)).subscribe() use take 1 it subscribe for one time then exit

  • 1
    Please add further details to expand on your answer, such as working code or documentation citations. – Community Sep 06 '21 at 12:54