DEV Community

Voltra
Voltra

Posted on • Edited on

2 1

JS Promises are monadic... just not the way you'd expect

Note that these apply mostly to Promise<NonPromiseType>. Some properties do not hold due to the "unwrapping" of nested promises.

FP definition

Based on Functional Programming definitions all we need are:

  • a type constructor: Promise<T> for any T
  • a unit / type converter: Promise.resolve(value)
  • a bind / combinator: Promise#then
  • have unit be left identity of bind (i.e. unit(x) >>= f <=> f x): Promise.resolve(x).then(f) is functionally the same as f(x)
  • have unit be right identity of bind (i.e. p >>= unit <=> p): myPromise.then(Promise.resolve) is functionally the same as myPromise
  • bind is associative (i.e. ma >>= λx → (f(x) >>= g <=> (ma >>= f) >>= g): promise.then(x => f(x).then(g)) is functionally the same as promise.then(f).then(g)

Thus, we have proven <Promise, Promise.resolve, Promise#then> is a monad.

Where it blocks

JS promises have one cool trick that makes them viable instead of all the alternatives we ever had (e.g. jQuery's deferred): they unwrap themselves.

For instance: Promise.resolve(2).then(x => Promise.resolve(x * x) is a Promise<int> and not a Promise<Promise<int>>. There are two ways to look at this:

  • Nested promises cannot exist as a type
  • Nested promises are aliases to their non-nested equivalents

The problems with the proof above is as follows:

const x = Promise.resolve(2);
const f = px => px.then(x => x * 2);

f(x) !== Promise.resolve(x).then(f);
Enter fullscreen mode Exit fullscreen mode

Because Promise#then is a continuation that gets a value as input (and not a promise), we cannot hold the left identity of unit of Promises for promises: we can only hold the left identity of Promises for values. That's because Promise#then plays the role of both map and flatMap.

Obviously this is the only situation this will bite you in the ass if you consider Promises monadic, otherwise you're absolutely free to treat them as that and start rewriting non async/await code to proper clean promise-based code:

promise.then(x => {
 return fetch(x).then(f);
});

// <=>

promise
  .then(fetch)
  .then(f);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!