Promise vs. Observable

Recently I was involved in designing an authentication service for a web application being developed using Angular framework. One interesting decision we ha to make was whether the service’s publicly exposed asynchronous functions should return promises or observables.

Observables in a Nutshell

A promise represents the result of an asynchronous operation. This can be a value in case of success or an error in case of failure. In contrast, an observable represents a sequence of values that are delivered as a result of an asynchronous operation. This however is an oversimplification. I highly recommend this talk by Zaven Muradyan which does an excellent job of explaining observable concept in more depth.

Another interesting key difference is that typically observables are lazy. So unlike promises they do not execute immediately upon creation instead they run when they are subscribe too. This make them much better suited for construction of reusable transformations (via pipe) that can be executed at a later time.

Observables are a core abstraction that in the Angular framework itself and are heavily used by many of built-in modules. Because of this applications that are based on the Angular framework tend to embrace the Observable abstraction too. Beyond providing a more consistent API, it also allows the application to extend the lazy evaluation semantic beyond the Angular core and to the rest of the application.

Promise or Observable?

The authentication service public API was a user$ property which is of type Observable<User|null>. All other parts of the application can observe it (by calling subscribe on it) to react on changes to the current user. Observable abstraction is a great fit here since user login state can change multiple time during a single session.

The authentication service also exposes two operations: sign-in and sign-out. Both are implemented as asynchronous methods that return a single value on success or error on failure.

Our first stab at this was to use a Promise. It felt natural since the promise semantics are a good fit:

  1. An asynchronous operation that produces a single value or fails.
  2. The operation is meant to be executed immediately.

Here is the Promise-based version:

export class AuthService {
  private readonly userSubject$ = new BehaviorSubject<User | null>(null);

  get user$(): Observable<User | null> {
    return this.userSubject$.asObservable();
  }
  
  signIn(
    email: string,
    password: string
  ): Promise<void> { 
    /* Sign in, and on success update the user$ and resolve the promise. */
  }

  signOut(): Promise<void> { 
    /* Sign out, and on success update the user$ to nil and resolve the promise. */
  }
}

The key disadvantage is that it was different from the rest of our module APIs which by and large use observables for asynchronous operations.

We ended up scraping this design and opting to use observable for method return types despite the fact that these operations don’t really take advantages of observable semantics (i.e., multiple values and its lazy behavior). The main argument was that the value of overall consistency in our API beats the minor improvement in readability and semantic that we may get by using a promise instead.

Here is our final design that we landed on:

export class AuthService {
  private readonly userSubject$ = new BehaviorSubject<User | null>(null);

  /**
   * An observable that is updated as the current user changes.
   */
  get user$(): Observable<User | null> {
    return this.userSubject$.asObservable();
  }

  signIn(
    email: string,
    password: string
  ): Observable<void> { }

  signOut(): Observable<void> {}
}

Reasonable Engineers May Disagree

It is worth pointing our that like many other API design decisions the alternative is often a reasonable choice when given a different design constraints. As an example, Angularfire, which is Firebase’s popular Angular library, opts for the first design which mixes both observables and promises.