Posted on:November 8, 2020

Promises in JavaScript are almost monads. With ordinary values, they behave like monads. However, they have some edge behaviour that disqualifies them from attaining monadic status.

In order for something to be considered a monad, two operations must be defined1. Scala calls them `unit` and `flatMap`, whereas 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``````

Additionally, these two operations must fulfill certain laws:

## #Left Identity

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

## #Right Identity

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

## #Associativity

``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 analogues of `unit` and `flatMap` in JavaScript promises are `Promise.resolve` and `Promise.then`.

As you can see below, promises do behave like monads for most ordinary values.

For left identity:

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

For right identity:

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

And lastly for associativity:

``````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? Remember the Monad laws should hold, even if the value of `a` is itself already a Monad instance. In Scala, this could be represented by a value like `Option[Option[A]]`. It’s up to you to assign semantic meaning to a value like this, but an example could the result of 2 sequential network calls, either of which has a significant chance of failing.

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

This is where JavaScript promises fall flat. Promises don’t like being nested. They will automatically resolve whatever is inside if it happens to a thenable. To demonstrate this, see below:

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

Or an equivalent thenable:

``````> 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 expected, but the second line prints `undefined`. This is because instead of passing a “nested” promise to `.then(f)`, `Promise.resolve` “unwraps” the thenable, passing only a singly-wrapped promise. So what `f` ends up receiving in its argument is the function `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 identity law is violated.

# #Footnotes

## #Footnotes

1. There is more than one way to specify this combination of operations, and they are all equivalent. Instead of implementing `unit` and `flatMap`, one could also implement `unit`, `map`, and `join`, or `unit` and `compose`. `join`’s function signature is `F[F[A]] => F[A]`, whereas `compose`’s signature is `(A => F[B], B => F[C]) => (A => F[C])`.