The Promise of a Promise

David Wickström
Aug 24, 2017 · 9 min read

Promises in JavaScript is an incredibly powerful thing to have in one’s toolbox. The nature of applications written in JS are often relatively complex due to being time dependent in different ways. Without tools like the Promise these things quickly become much harder to handle.

I’ve been learning about Monads and functional programming in general for a while now. Especially learning about Monads has helped my understanding of the more fine grained details of the Promise.

Promises are specified in an open standard called Promises/A+. Uncovering its inner workings and specific behaviours is really interesting, and even more so when considering what Monads are. Hint: they are similar.

Promises/A+

The Promise is sometimes referred to as being thenable. The then method always gives you a Promise back (at the return position of the call) and either a value at the first argument position or an error value at the second argument position (the second callback argument being optional).

The style of not delivering a value at the return position but rather through a continuation is also referred to as CPS, Continuation-passing style.

Also from the spec:

onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

This detail is what makes it able to handle time dependent, non-blocking functions.

Continuation Monad

The Continuation Monad, in Haskell, or Task in scalaz are tools that solve the same problem as the Promise does. A way of dealing with time dependent side effects, whilst avoiding callback hell. These tools exist, and they adhere to generic Monadic interfaces as well the necessary rules and traits that give them their specific identities.

Monadic interface

In functional languages, things that are Monads have a very specific interface. They specifically have two functions with very specific behaviour that go with them. These methods have different names in different languages. From here on I’ll name them in the order of Haskell/Scala/JavaScript. Note that in JavaScript there is no formal standard for Monadic interfaces, but an open standard called Fantasy Land.

Fantasy Land logotype

is return/unit/of. This just allows you to put a value into the Monad. Like wrapping on your gift. Basically a constructor function, a.k.a. type constructor. This method is also sometimes referred to as point.

Monad m => a -> m a// m is Monad

You might recognize this method from Array.prototype.of. This is just a function that puts your value into the Monad. Literally like putting a thing in a box. This "box" thing is sometimes referred to as "context".

is bind/flatMap/chain which will let you apply functions to the inner value, functions that return a new Monad, whilst unwrapping the current one.

This specific behaviour lets you chain functions from a -> m a in such a way that when you are done chaining you will still just have your (transformed) value wrapped in the same Monadic type. In short: it avoids the nesting of your wrapping Monad by lifting the value from the inner contexts into the outer context.

Imagine a function that puts a value in a list.

const duplicate = x => [x, x]

Now apply this function to a value in a list. You would end up with a list of a list of a value.

[1].map(duplicate) // [[1, 1]]

Enter bind/flatMap/chain.

[1].chain(duplicate) // [1, 1]

In Scala this function is called flatMap - it maps a function onto a value in the context while performing flattening of the contexts. Makes sense.

Which brings me to some of the other things that makes the Monad a Monad. A Monad is also a Functor and a Functor is something that you can map over. Like Array.prototype which is indeed a Functor thanks to its map method. Array.prototype also happens to be a Pointed Functor[2] because it has an Array.prototype.of method, remember? return/unit/of, point?

All these types are connected beautifully as you can see in this diagram.

Borrowed without any permission from https://github.com/fantasyland/fantasy-land

The Functor will give you the map method, the Applicative will give you a way to put values into a context, through the return/unit/of constructor. The Chain type will give the ability to perform bind/flatMap/chain. Together these traits form a Monad.

There are plenty of relations to be discovered here but I’ll save them for another time.

Now, back to the Promise for some testing.

Monadic interface of the Promise

In order for a thing to be a Monad it must provide some functionality as well as obey some laws. Let’s try the functionality first.

1.

Answer: Yes. While in the other (functional) languages there is a more generic, monadic interface for this function, the Promise on the other hand has a very specific interface. Namely a then method that will let you do just that.

const inc = x => x + 1const getNumber = () =>
new Promise((resolve, reject) => resolve(10))
getNumber()
.then(inc)
.then(console.log) // 11

In this case the onFulfilled callback (inc) has the type signature of

inc :: a -> a

2.

Answer: Yes.

const foo = Promise.resolve(33)
foo.then(console.log) // 33

Again, not too generic but specific to the Promise interface. Note that you can’t put a value into a Promise without setting its state into being either a resolved or a rejected. Seems a little foreign at first, but after a few moments of thought it makes sense.

3.

Answer: Yes yes. While the functional approach is to provide a specific method for this specific behaviour for all types, in JavaScript this behaviour is encapsulated in a very specific method called then. Aha! So what we are looking at here is a single method that tries to perform both the duties of fmap/map/map and bind/flatMap/map.

Essentially, the Promises/A+ specification says that if the then is given a callback that will return something that has then has (if it walks like a duck… remember)?, subsequently there flattening of the contexts will be applied. Meaning, there will not be any nesting of Promises going on.

const double = x =>
new Promise((resolve, reject) => resolve(x * 2))
getNumber()
.then(inc)
.then(double)
.then(console.log) // Promise of 22

In this case the function double has a type signature of

double :: a -> Promise a

…which the Promise detects and then subsequently

  1. removes the value from the first Promise
  2. passes that value to the onFulfilled callback
  3. returns the executed onFulfilled callback

…which in this case happens to return a new Promise.


Note that this design decision (the type checking of the callback signature) did cause some controversy, where the nay sayers claimed it would introduce unnecessary complexity.

Is the Promise then a Monad?

A Monad must provide some methods, like the above, and it must also obey some laws. Mathematical laws that is. Let’s have a look.

Left identity

// Haskell
return x >>= f === f x
// Scala
unit(x).flatMap(f) == f(x)
// Fantasy Land JavaScript
M.of(x).chain(f) === f(x)

Ok, lets try that.

const inc = x => Promise.resolve(x + 1)Promise.resolve(1).then(inc) // Promise of 2
inc(1) // Promise of 2

Ok, great. One down, two to go.

Right identity

// Haskell
m >>= return === m
// Scala
m.flatMap(unit) == m
// Fantasy Land JavaScript
m.chain(M.of) === m

Let’s see now

const promiseOf = x => Promise.resolve(x)const m = Promise.resolve(1)
m === m.then(promiseOf)

Fine. Let´s try another law.

Associativity

// Haskell
(m >>= f) >>= g === m >>= (\x -> f x >>= g)
// Scala
m.flatMap(f).flatMap(g) == m.flatMap(x => f(x).flatMap(g))
// Fantasy Land JavaScript
m.chain(f).chain(g) === m.chain(x => f(x).chain(g))

Okay now in Promise land…

const inc = x => Promise.resolve(x + 1)
const dec = x => Promise.resolve(x - 1)
Promise.resolve(1)
.then(inc)
.then(dec) // Promise of 1
===Promise.resolve(1)
.then(x => inc(x)
.then(y => dec(y))) // Promise of 1

Holds!


As a side note I’m actually cheating a little bit because object comparison in JavaScript works in such a way that it wants first and the second object to refer to the same memory location, in order for the equality expression to evaluate to true. But that’s another blog post.

Total score

3/3 laws hold. Pretty close to a Monad, no?

Random thoughts

So then, is the Promise a proper Monad? I would say so, and apparently many others have considered it a proven fact.

But it would probably have been better off if someone would have considered it more carefully when designing the spec. Had they not turned the blind eye on the resemblance of the Monad, then we might have had an even better, more generic and ultimately a more powerful Promise API.

Scary words

I sometimes see debates where people argue that the namings from Category Theory (like Monads, Monoids, Functors etc…) does not make any sense, that they are inaccessible and even elitist. Personally I think that the people who formed this branch of Math maybe would have wanted names for each of their entities defined that would explain its identity just by the names of them. But maybe the reason that the names instead became Functor, Monad, Semigroup, is because their traits are too complex to be condensed into a single word like Concatable or Chainable. At the end of the day, a Monad for example, in OO speak, implements a whole bunch of interfaces: Concatable, Mappable, Chainable, Applicable and what have you.

How do you really name something that has all these traits in a way that makes you fully grasp what it is?

Onwards

Anyhow. What I would like to do is to start using something, in JavaScript, that will add some of the missing pieces from the Promise A+ standard.

Most importantly: A thing that provides lazy execution of side effects, to enable composability.

Basically to provide the ability to use function composition not only with small pure functions, but also with functions that perform side effects.

But why? I hear you say. Because I want to get better at writing safer, more accurate programs. To be able to prove that they are accurate, even. This will enable me to build and maintain more complex programs without sacrificing quality and safety.

Alternatives to Promises

Nifty tools

Futurize is a very nifty tool to help you convert either a CPS style function or a Promise into a Future type. It's compatible with data.task from Folktale and some other libraries as well. Quite a handy tool that saves some effort.

Paradigm overlap

Did the Promise specification come about as an attempt to simplify or improve what is already been cooking for a long long time in functional programming environments?

Here is a great thread that sheds some light on what went down when the Promises/A+ specification came about some 4 years ago. Looks like people in the one hand corner thinks that they new (the Promise) while people in the other corner tries to point out that they not invented but (the Monad, like many others before them).

To me it’s seems odd to build something that is so similar to ready made concepts and then deliberately avoiding making it 100% compatible, whilst also trying to defend why somewhat older concepts like algebra[3] and equational reasoning and other things are invalid in the context of JavaScript.

The thread mentioned above is a few years old now and I think it’s fair to say that now the arrows[4] are pointing in favour of those arguing for a more algebraic reality[5].

Check out Reason for example. It’s not hard to see where Facebook wants things to go.

Also, don’t forget:


1: This bit is not in the specification

2: A good source of info on Pointed Functors and Functors in general

3: Pretty old

4: Pun intended

5: Rather dystopian name for this standard? Or ironic maybe?

A3J dev blog

Thoughts and reflections from a Stockholm based web tech agency

)
David Wickström

Written by

Developer - JS, Python, PHP and more

A3J dev blog

Thoughts and reflections from a Stockholm based web tech agency

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade