138

I'd like to be able to await on an observable, e.g.

const source = Rx.Observable.create(/* ... */)
//...
await source;

A naive attempt results in the await resolving immediately and not blocking execution

Edit: The pseudocode for my full intended use case is:

if (condition) {
  await observable;
}
// a bunch of other code

I understand that I can move the other code into another separate function and pass it into the subscribe callback, but I'm hoping to be able to avoid that.

CheapSteaks
  • 4,091
  • 3
  • 28
  • 46

6 Answers6

163

You have to pass a promise to await. Convert the observable's next event to a promise and await that.

if (condition) {
  await observable.first().toPromise();
}

Edit note: This answer originally used .take(1) but was changed to use .first() which avoids the issue of the Promise never resolving if the stream ends before a value comes through.

Macil
  • 2,916
  • 1
  • 19
  • 17
  • 3
    Instead of take(1) could you use `await observable.first().toPromise();`? – apricity Oct 21 '16 at 18:54
  • 1
    @apricity That should come out the same. – Macil Oct 21 '16 at 20:30
  • 16
    @apricity If there were no values on completion, `first()` will result in rejection, and `take(1)` will result in pending promise. – Estus Flask Dec 02 '16 at 10:12
  • @estus: So, in general, it makes more sense to call `first()`, right? – tokland May 30 '17 at 09:03
  • 8
    @apricity @AgentME Actually you should NOT use either `take(1)` nor `first()`in cases like this. Since you are expecting exactly ONE event to happen you should use `single()` which will throw an exception if there is more than 1,while not throwing an exception when there is none. If there is more than one there is likely something wrong in your code / data model etc. If you do not use single you will end up arbitrarily picking the first item that returns without warning that there are more. You would have to take care in your predicate on upstream data source to always maintain the same order. – ntziolis Jun 22 '17 at 12:47
  • 1
    @ntziolis Note that .single() waits until the source stream ends before it even outputs the value and ends, which may not work well in all situations that .first() or .take(1) would work. – Macil Jun 22 '17 at 18:58
  • 4
    Don't forget the import: `import 'rxjs/add/operator/first';` – Stephanie Feb 14 '18 at 12:29
  • 9
    Now that toPromise() is deprecated, how should we do this? – Jus10 Aug 03 '18 at 17:53
  • 4
    @Jus10 I can't find anything to indicate `toPromise()` is deprecated. Where did you read that? – NickL Jun 12 '19 at 12:47
  • 1
    `import { first } from 'rxjs/operators';` now – ps2goat Aug 02 '19 at 21:15
  • 2
    @EstusFlask Actually `take(1)` will **not** yield a pending promise. It will yield a promise resolved with `undefined`. – Johan t Hart Jan 10 '20 at 10:31
  • 1
    Maybe it worked before, but now `pipe` is needed: `observable.pipe(first()).toPromise()`. – Qortex Jun 23 '20 at 15:25
  • 2
    Use the new ` firstValueFrom()` or `lastValueFrom()` instead of toPromise. Available in RxJS 7+. See : https://indepth.dev/rxjs-heads-up-topromise-is-being-deprecated/ – TetraDev Sep 04 '20 at 16:25
  • 1
    I've used just `observable.toPromise()` many times without issue. If you really only need the first one, then use `observable.first().toPromise()` - otherwise the plain implementation will take the *last value after completion* – TetraDev Sep 04 '20 at 18:07
27

It likely has to be

await observable.first().toPromise();

As it was noted in comments before, there is substantial difference between take(1) and first() operators when there is empty completed observable.

Observable.empty().first().toPromise() will result in rejection with EmptyError that can be handled accordingly, because there really was no value.

And Observable.empty().take(1).toPromise() will result in resolution with undefined value.

Estus Flask
  • 150,909
  • 47
  • 291
  • 441
16

Use the new firstValueFrom() or lastValueFrom() instead of toPromise(), which as pointed out here, is deprecated starting in RxJS 7, and will be removed in RxJS 8.

import { firstValueFrom} from 'rxjs';
import { lastValueFrom } from 'rxjs';

this.myProp = await firstValueFrom(myObservable$);
this.myProp = await lastValueFrom(myObservable$);

This is available in RxJS 7+

See: https://indepth.dev/rxjs-heads-up-topromise-is-being-deprecated/

TetraDev
  • 12,675
  • 2
  • 48
  • 54
11

You will need to await a promise, so you will want to use toPromise(). See this for more details on toPromise().

Josh Durham
  • 1,572
  • 1
  • 16
  • 27
7

If toPromise is deprecated for you, you can use .pipe(take(1)).toPromise but as you can see here it's not deprecated.

So please juste use toPromise (RxJs 6) as said:

//return basic observable
const sample = val => Rx.Observable.of(val).delay(5000);
//convert basic observable to promise
const example = sample('First Example')
  .toPromise()
  //output: 'First Example'
  .then(result => {
    console.log('From Promise:', result);
  });

async/await example:

//return basic observable
const sample = val => Rx.Observable.of(val).delay(5000);
//convert basic observable to promise
const example = await sample('First Example').toPromise()
// output: 'First Example'
console.log('From Promise:', result);

Read more here.

And please remove this wrong claim saying toPromise is deprecated.

Emerica
  • 3,711
  • 2
  • 26
  • 34
2

Using toPromise() is not recommended as it is getting depreciated in RxJs 7 onwards. You can use two new operators present in RxJs 7 lastValueFrom() and firstValueFrom(). More details can be found here

const result = await lastValueFrom(myObservable$);

Implementations in Beta version are available here: