Sweeten your Promise with Monad in ReasonML
Promise is one of the most I found useful construct in the web programming. Because of its async nature. Promise provides a nice way to put a code into synchronous chunks and each chunk is called at an asynchronous point of time.
Suppose we want to get data from two API endpoints in an orderly fashion. We should not perform a blocking call on each API fetch because it against the nature of JavaScript, Let alone that it does not even consider acceptable from a user perspective. All UI components will be unusable, and it will freeze the browser. One must have a very good reason to try to do it.
An asynchronous solution can be done with a Promise object. It will keep you a promise, to come back with a value after finish the execution. The code goes like this:
The promise object will be created by fetch command, and then the response object will be sent and appear in the next .then
as a function argument. The code above is little complicated as it hits a URL first and then grab a JSON content of the URL. But I want to keep it this way to close to a real-life use case.
The image above shows how a promise object sends their results to next `.then` . The blocks represent execution context of each function. The difference to a normal function call is that they are async at the point of call but it will wait until a function resolve (or reject) is called before executing next block.
The result of Promise will be always a Promise. Even resolve
is not called, Promise will take a return value and pass it to the next execution. To this point, one might feel familiar with the notion of cannot escape the box. YES, Promise is a monad. To keep this article short, I will show that it is a monad in another article, or you can prove it yourself that it satisfies two properties, namely, return
and >>=
.
For now, let us get back to the title of this article. ReasonML provides a binding for Promise in Js.Promise
object. We can achieve the same code above in ReasonML in this way:
The code above does the same thing as the JavaScript version. Every time we want to start a new block of execution, we have to put into then_
keyword, which is wasting time and make the code harder to read. We can put monadic expression at work to sweeten our code a bit. I defined the PromiseMonad as shown below:
Actually, to make it become a Monad we only need return
and (>>=)
operator. defer
is there just to make my life simpler to create the promise object that gonna resolve in the first place. Let us take a close look at their type
return: a' => Js.Promise.t('a)
That is a simple lift, it will take any type and wrap it in the Promise type. Now let’s take a look that our workhorse, the bind function
(>>=): (Js.Promise.t('a),'a=>Js.Promise.t('b))=> Js.Promise.t('b)
Trivially, it takes
- Promise of any type
'a
- a function that takes the same type
'a
that will produce a Promise of another type'b
.
This bind functions will produce a Promise
of the other type'b
for you!!!
Using the Monad we defined will be easy if the code doesn’t require a function call that return the value from another normal function.
It seems like we need another function that helps us kick start the lazy Monad. This function should take any function type and return us Promise of any type we want. That is why defer
come to rescue us. The function type goes like this:
defer: (. 'a) => unit => 'b => Js.Promise.t('a)
which basically means, it takes a function
and it will return a Promise object of any type that the function
is return. The fancy (. 'a)
is a ReasonML syntax that it is to create JavaScript object, not a record type. That is the requirement of make
function. Maybe, it will become more clear when we see how to use it.
defer
function is generic enough to take in any function and wrap it with a nice, warm Promise.
Lastly, we are not forget to visit the code at the top of this article and see how to rewrite that using new monadic style. As a result, we can use promise with clearer syntax and the code is now sweeter~
Note: I created repo for this little tool and also npm package for humanity.