November 08, 2020

Promis­es in JavaScript are almost monads. With ordi­nary values, they behave like monads. How­ev­er, they have some edge behav­iour that dis­qual­i­fies them from attain­ing monadic status.

In order for some­thing to be con­sid­ered a monad, two oper­a­tions must be defined1. Scala calls them `unit` and `flatMap`, where­as Haskell calls them `return` and `>>=`.

``````def unit[A](a: A): F[A]
def flatMap[A,B](a: F[A], f: A => F[B]): F[B]``````
``````return :: Monad m => a -> m a
(>>=) :: Monad m => m a -> (a -> m b) -> m b``````

Addi­tion­al­ly, these two oper­a­tions must ful­fill cer­tain laws:

## Left Iden­ti­ty

``flatMap(unit(a), f) == f(a)``
``````def f(x: Int) = Some(x + 1)
Some(1).flatMap(f) == f(1)``````

## Right Iden­ti­ty

``flatMap(a, x => unit(x)) == a``
``Some(1).flatMap(x => Some(x)) == Some(1)``

## Asso­cia­tiv­i­ty

``flatMap(flatMap(a, f), g) == flatMap(a, x => flatMap(f(x), g))``
``````def f(x: Int) = Some(x + 1)
def g(x: Int) = Some(x + 2)
Some(1).flatMap(f).flatMap(g) == Some(1).flatMap(x => f(x).flatMap(g))``````

These three laws must hold for all values of `a`, `f`, and `g`.

The ana­logues of `unit` and `flatMap` in JavaScript promis­es are `Promise.resolve` and `Promise.then`.

As you can see below, promis­es do behave like monads for most ordi­nary values.

For left iden­ti­ty:

``````const f = (x) => Promise.resolve(x + 1)
Promise.resolve(1).then(f) // Promise { 2 }
f(1) // Promise { 2 }``````

For right iden­ti­ty:

``````Promise.resolve(1).then(x => Promise.resolve(x)) // Promise { 1 }
Promise.resolve(1) // Promise { 1 }``````

And lastly for asso­cia­tiv­i­ty:

``````const g = (x) => Promise.resolve(x + 2)
Promise.resolve(1).then(f).then(g) // Promise { 4 }
Promise.resolve(1).then(x => f(x).then(g)) // Promise { 4 }``````

So where’s the catch? Remem­ber the Monad laws should hold, even if the value of `a` is itself already a Monad instance. In Scala, this could be rep­re­sent­ed by a value like `Option[Option[A]]`. It’s up to you to assign seman­tic mean­ing to a value like this, but an exam­ple could the result of 2 sequen­tial net­work calls, either of which has a sig­nif­i­cant chance of fail­ing.

``Some(Some(2)).flatMap(x => x.flatMap(y => Some(y + 2))) == Some(Some(4))``

This is where JavaScript promis­es fall flat. Promis­es don’t like being nested. They will auto­mat­i­cal­ly resolve what­ev­er is inside if it hap­pens to a then­able. To demon­strate this, see below:

``````> Promise.resolve(Promise.resolve(2))
Promise { 2 }``````

Or an equiv­a­lent then­able:

``````> Promise.resolve({ then: (x) => x(2) })
Promise { 2 }``````

So how does this break the Monad laws?

``````const obj = { then: x => x(2) }

const f = x => Promise.resolve(x.then)

f(obj).then(res => console.log(res))

Promise.resolve(obj).then(f).then(res => console.log(res))``````

If you run the above, The first line prints `x => x(2)`, as expect­ed, but the second line prints `undefined`. This is because instead of pass­ing a “nested” promise to `.then(f)`, `Promise.resolve` “unwraps” the then­able, pass­ing only a singly-wrapped promise. So what `f` ends up receiv­ing in its argu­ment is the func­tion `x => x(2)`, instead of the entire `{ then: x => x(2) }` object.

If you refer back to the laws, you’ll see that the left iden­ti­ty law is vio­lat­ed.

# Foot­notes

1. There is more than one way to spec­i­fy this com­bi­na­tion of oper­a­tions, and they are all equiv­a­lent. Instead of imple­ment­ing `unit` and `flatMap`, one could also imple­ment `unit`, `map`, and `join`, or `unit` and `compose`. `join`’s func­tion sig­na­ture is `F[F[A]] => F[A]`, where­as `compose`’s sig­na­ture is `(A => F[B], B => F[C]) => (A => F[C])`.