RxJS: How to Use Lettable Operators and Promises
October 03, 2017 • 2 minute read
With the release of RxJS 5.5.0-beta.5, toPromise
is now attached to Observable.prototype
, rendering this look at its ‘lettable’ variant a historical curiosity.
Converting observables to promises is an antipattern. Unless you are integrating observables with a promise-based API, there is no reason to convert an observable into a promise.
If you are integrating with such an API, in his article RxJS Observable interop with Promises and Async-Await, Ben Lesh shows how it can be done. However, that article uses the prototype-patched toPromise
method. Let’s have a look at the how the ‘lettable’ toPromise
function — introduced in the RxJS 5.5.0 beta — could be used.
With toPromise
, ‘lettable’ is something of a misnomer, as it cannot be used with the let
operator. Lettable operators are higher-order functions that return functions that receive and return observables. The toPromise
higher-order function returns a function that receives an observable, but it returns a promise — so it’s not lettable.
To convert an observable to a promise, toPromise
needs to be called and the observable needs to be passed to the function it returns, like this:
import { range } from "rxjs/observable/range";
import { map, filter, scan, toArray, toPromise } from "rxjs/operators";
const value$ = range(0, 10).pipe(
filter((x) => x % 2 === 0),
map((x) => x + x),
scan((acc, x) => acc + x, 0),
toArray()
);
const value = toPromise()(value$);
value.then((x) => console.log(x)); // [0, 4, 12, 24, 40]
The toPromise
call cannot be placed within the call to pipe
, as pipe
will only accept functions that receive and return observables. Instead, it must be called separately.
Usually, toPromise
would be called without an argument. However, if a particular promise implementation is to be used, a constructor can be passed. For example, a Bluebird promise could used, like this:
import { Promise as Bluebird } from "bluebird";
import { of } from "rxjs/observable/of";
import { toPromise } from "rxjs/operators";
const value$ = of("foo");
const value = toPromise(Bluebird as any)(value$);
value.then((x) => console.log(x)); // "foo"
An any
assertion is required because Bluebird does not have a Symbol.species
property, making TypeScript deem it incompatible with typeof Promise.
If the TC39 pipeline operator proposal is eventually accepted into the ECMAScript standard, there will be an alternative, arguably-more-natural syntax, that looks like this:
import { range } from "rxjs/observable/range";
import { map, filter, scan, toArray } from "rxjs/operators";
const value = range(0, 10)
|> filter(x => x % 2 === 0)
|> map(x => x + x)
|> scan((acc, x) => acc + x, 0)
|> toArray()
|> toPromise();
value.then(x => console.log(x)); // [0, 4, 12, 24, 40]