In RxJS 7 toPromise will become deprecated and with RxJS 8 - it will be gone! This is a heads up that tries to prepare you for this breaking change.

What can we use instead?
In short: lastValueFrom or firstValueFrom - but read on to get the full picture.

In case you have missed it: Currently most of us are enjoying our reactive ride on RxJS 6, but RxJS 7 is already available as a beta version.

Before RxJS 6 and the introduction of  pipe-able operators we could have mistaken toPromise as an operator, but -  it is not.
It can't be used within the pipe function.

The toPromise function lives on the prototype of Observable and is a util method that is used to convert an Observable into a Promise.

Inside this function we subscribe to the Observable and resolve the Promise with the last emitted value - attention - when the Observable completes!  

  toPromise<T>(this: Observable<T>): Promise<T>;
  toPromise<T>(this: Observable<T>, PromiseCtor: typeof Promise): Promise<T>;
  toPromise<T>(this: Observable<T>, PromiseCtor: PromiseConstructorLike): Promise<T>;
  toPromise(promiseCtor?: PromiseConstructorLike): Promise<T> {
    promiseCtor = getPromiseCtor(promiseCtor);

    return new promiseCtor((resolve, reject) => {
      let value: any;
      this.subscribe((x: T) => value = x, (err: any) => reject(err), () => resolve(value));
    }) as Promise<T>;
  }
toPromise Source
  • this: Observable<T>: Type declaration of the this parameter which is describing the expected type of the implicit this object.
    This is a so-called fake parameter - which only exists at compile time - to avoid bugs when you pass a function around and change its this context without noticing it.
  • promiseCtor is a Promise constructor - enabling us to use it with different Promise implementations.
  • The important part: We are then wrapping the the subscribe call in a Promise which will resolve when the Observable's complete handler is called.

Examples#

When it comes to Angular most of you will be familiar dealing with Observables when we make HTTP requests using the built in HttpClient from Angular:

@Injectable({
  providedIn: 'root'
})
export class InventoryService {
  constructor(private httpClient: HttpClient) {}

  getCategories(): Observable<Category[]> {
    const url = 'https://www.themealdb.com/api/json/v1/1/categories.php';

    return this.httpClient.get<CategoriesResponse>(url).pipe(
      map(response => response.categories)
    );
  }
}
Example of making a HTTP-Request returning a Observable


Usage of the "old" toPromise
When we don't want to deal with a Observable in our component then we can currently use toPromise:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {
  categories: any[];

  constructor(private inventoryService: InventoryService) {}

  public async loadCategories() {
    this.categories = await this.inventoryService
      .getCategories()
      .toPromise()
  }
}
Example using toPromise


Usage of the "new" lastValueFrom:

From a functional perspective lastValueFrom is the function we should go for as  replacement of toPromise.

import { lastValueFrom } from 'rxjs';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {
  categories: any[];

  constructor(private inventoryService: InventoryService) {}

  public async loadCategories() {
    const categories$ = this.inventoryService.getCategories();
    this.categories = await lastValueFrom(categories$);
  }
}
Example using lastValueFrom

The change to await lastValueFrom(categories$) reads pretty nice, don't you think?

Usage of the "new" firstValueFrom

When we want to use firstValueFrom we are not interested in the completion of the stream at all. What we want is its first emitted value, resolve the Promise with it and unsubscribe from the stream.

This can be useful when we are dealing with a stream that is constantly emitting values and is not completing immediately.

As an example we could think of consuming a stream of events - pushed by the server - e.g using SSE's - Server Sent Events.
Then our InventoryService would use a EventSource to pass every received message into an Observable stream.

@Injectable({
  providedIn: "root"
})
export class InventoryService {
  constructor() {}

  getCateogries(): Observable<Category[]> {
    return Observable.create(observer => {
      const eventSource = new EventSource('https://www.themealdb.com/api/categories/events');
      eventSource.onmessage = event => {
        observer.next(event);
      };
      eventSource.onerror = error => {
        observer.error(error);
      };
      
      return () => {
        eventSource.close();
      };
    });
  }
}
Consuming Server-Sent-Events

When we are only interested in the first event that is arriving, we can now make use of firstValueFrom:

import { firstValueFrom } from 'rxjs';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent {
  categories: any[];

  constructor(private inventoryService: InventoryService) {}

  public async loadCategories() {
    const categories$ = this.inventoryService.getCategories();
    this.categories = await firstValueFrom(categories$);
  }
}
Example using firstValueFrom

Why is this happening to me?#

At first sight it wasn't totally clear why `toPromise is being deprecated.

Nicholas Jamieson - core team member of RxJS - helped us out here and provided us with some very insightful information:

  1. One goal was to remove it from the Observable prototype and turn it into a standalone util function.
  2. The naming of toPromise is not the best.
    Especially when used in combination with await it does not read very well:
    await categories$.toPromise() vs await lastValueFrom(categories$)
  3. The type information of toPromise is wrong.
    When the source Observable completed without ever emitting a single value - it resolved with undefined. It should reject in that case.
    A Promise is a "promise" that when it resolves a value will be there - and be it undefined. But when the stream completes without ever emitting a value you can't differentiate between a stream that a emitted undefined on purpose and a stream that completed without ever emitting anymore. More on that here.

Summary#

Avoid toPromise for future development and prefer the use of lastValueFrom / firstValueFrom.

toPromise is being deprecated in RxJS 7 and will be removed in RxJS 8.

I tried to avoid the "using Promises is a anti-pattern" topic on purpose here.
Feel free to discuss it in the comments, though.

When would you use Promise over Observable?
I can think of an API lib that additionally exposes Promises to external partners, who may don't use RxJS at all. It's also useful when using async-await in non-marble tests.

Special thanks to Nicholas and Lars for the help with this article - you both are a great inspiration for myself and our community.

I wish you a great day - thanks for stopping by.


Further reading material on the interoperability of Promises and RxJS