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>;
}
this: Observable<T>: Type declaration of thethisparameter which is describing the expected type of the implicitthisobject.
This is a so-called fake parameter – which only exists at compile time – to avoid bugs when you pass a function around and change itsthiscontext without noticing it.promiseCtoris a Promise constructor – enabling us to use it with differentPromiseimplementations.- The important part: We are then wrapping the the
subscribecall in aPromisewhich willresolvewhen theObservable‘scompletehandler 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](https://developer.mozilla.org/en-US/docs/Web/API/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:
- One goal was to remove it from the
Observableprototype and turn it into a standalone util function. - The naming of
toPromiseis not the best.
Especially when used in combination withawaitit does not read very well:
await categories$.toPromise()vsawait lastValueFrom(categories$) - The type information of
toPromiseis wrong.
When the sourceObservablecompleted without ever emitting a single value – it resolved withundefined. It should reject in that case.
APromiseis a “promise” that when it resolves a value will be there – and be itundefined. But when the stream completes without ever emitting a value you can’t differentiate between a stream that a emittedundefinedon 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