Wanna see something cool? Check out Angular Spotify 🎧
All Articles

Convert Promise to Observable

Promise is the answer to the callback hell problem. With the introduction of async/await syntax, Promise is getting so popular. But if you work with Angular, you’ll probably need an Observable from Promise sometimes. There are few ways for you to do so.

Assume that I have a function, getPromise, that resolves after two seconds and does a simple console.log.

const getPromise = (source: string) => {
  return new Promise(resolve => {
    console.log('Promise created for', source)
    setTimeout(() => resolve(`Promise Resolved: ${source}`), 2000)
  })
}

1. Direct Execution / Conversion

Use from to directly convert a previously created Promise to an Observable.

import { from } from 'rxjs'

// getPromise() is called once, the promise is passed to the Observable
const observableFrom$ = from(getPromise('FROM'))
observableFrom$.subscribe(console.log)
observableFrom$.subscribe(console.log)

observableFrom$ will be a hot Observable that effectively replays the Promises value to Subscribers.

It’s a hot Observable because the producer (in this case, the Promise) is created outside of the Observable. And because Promise is eager, you’ll start seeing Promise created for FROM on the browser when running from(getPromise()) console immediately after you call from(getPromise("FROM"))

Promise created for FROM
Promise Resolved: FROM
Promise Resolved: FROM

Multiple subscribers will share the same Promises, which means if you subscribe to observable$ multiple times, you’ll still see only one Promise created for FROM.

2. Deferred Execution On Every Subscribe

Use defer with a Promise factory function as input to defer the creation and conversion of a Promise to an Observable.

import { defer } from 'rxjs'

// getPromise() is called every time someone subscribes to the observable$
const observableDefer$ = defer(() => getPromise('DEFER'))
observableDefer$.subscribe(console.log)
observableDefer$.subscribe(console.log)

observableDefer$ here will be a cold Observable.

It’s a cold Observable because the producer (the Promise) is created inside of the Observable. Each subscriber will create a new Promise by calling the given Promise factory function.

This allows you to create an observableDefer$ without creating and thus executing a Promise right away and without sharing this Promise with multiple subscribers. Each subscriber to observableDefer$ effectively calls from(promiseFactory()).subscribe(subscriber). Meaning if you subscribe to observableDefer$ multiple times, you’ll see Promise created for FROM each time.

Promise created for DEFER
Promise created for DEFER
Promise Resolved: DEFER
Promise Resolved: DEFER

3. Many Operators Accept Promises Directly

Most RxJS operators that combine (e.g. merge, concat, forkJoin, combineLatest …) or transform observables (e.g. switchMap, mergeMap, concatMap, catchError …) accept promises directly. If you’re using one of them anyway, you don’t have to use from to wrap a promise first (but to create a cold observable you still might have to use defer).

// Execute two promises simultaneously
forkJoin(getPromise(1), getPromise(2)).pipe(
  switchMap(([v1, v2]) => v1.getPromise(v2)) // map to nested Promise
)

Check the documentation or implementation to see if the operator you’re using accepts ObservableInput or SubscribableOrPromise.

type ObservableInput<T> = SubscribableOrPromise<T> | ArrayLike<T> | Iterable<T>
// Note the PromiseLike ----------------------------------------------------v
type SubscribableOrPromise<T> =
  | Subscribable<T>
  | Subscribable<never>
  | PromiseLike<T>
  | InteropObservable<T>

Source code

Reference

Convert Promise to Observable - answer by frido

Published 7 Mar 2021

Recent Posts

Capture picture from your Webcam in Angular

A simple walk through how to use the browser MediaDevices for capturing picture from a webcam in Angular

Migrate Angular to ESLint

I'll show you how to migrate from TSLint to ESLint and using husky to run lint every time you try to make a commit


Follow @tuantrungvo on Twitter for more!