I was using Bluebird for doing asynchronous stuff, but now have to do a lot of empty / null / error checks and I don't want to go down the usual if Else route. Am thinking of using monads, but have not yet grokked it completely.
Also I want it to play nicely with ramda's pipe / compose
as most of my other code is neatly encapsulated in functional pipelines. According to many discussions, monadic Futures (Fluture seems to be recommended) are preferred over Promises and support for pipeP and composeP may be removed in future versions.
Fluture seems like a good option as it supposedly plays well with libraries (like ramda) that adhere to fantasy-land specs.
However I am completely lost as to how to go about implementing stuff integrating Ramda's pipe with Fluture. I need help with some example code.
For eg:
I have a DB call that returns an array of Objects. The array may have values, be empty or be undefined. I have a functional pipeline that transforms the data and returns it to the front end.
Sample Promise code:
fancyDBCall1(constraints)
.then(data => {
if (!data || data.length === 0) {
return []
}
return pipe(
...
transformation functions
...
)(data)
})
.then(res.ok)
.catch(res.serverError)
Can somebody give some pointers on a good way to proceed.
What can you do ?
So, there are a few things when can do with your code. But first, let's talk about Monads.
In this code there are 3 types of Monads you can use:
nothing
)Maybe this, maybe not!
Let's decompose your code a little bit. The first thing we want to do is to make sure your
fancyDBCall1(constraints)
returns aMaybe
. This means that it maybe returns a result, or nothing.However, your
fancyDBCall1
is an async operation. This means that it must return aFuture
. The trick here is instead of making it return a future of a value, likeFuture <Array>
to make it return aFuture < Maybe Array >
.Whoa, that sounds complicated mister!
Just think of it like instead of having:
Future.of('world');
You have:
Future.of( Maybe( 'world' ) );
Not so bad right?
This way you avoid doing null checks in your code! The following lines would disappear:
And your example would look something like:
See how nice our code looks? But wait, there is more!
We Either go further, or we don't!
So, if you are paying close attention, you know I am missing something. Sure, we are now handling a null check, but what if
transform
blows up? Well, you will say "We send res.serverError".Ok. That's fair. But what if the
transform
function fails because of an invalid username, for example?You will say your server blew up, but it wasn't exactly true. Your async query was fine, but the data we got wans't. This is something we could anticipate, it's not like a meteor hit our server farm, it's just that some user gave us an invalid e-mail and we need to tell him!
The trick here would be go change our
transform
function:Wow, Jesus bananas! What is this dark magic?
Here we say that our transform maybe returns Nothing or maybe it returns an Either. This Either is either a string ( left branch is always the error ) or an array of values ( right branch is always the correct result ! ).
Putting it all together
So yeah, it has been quite a hell of a trip, wouldn't you say? To give you some concrete code for you to sink your teeth in, here is what some code with these constructs could possibly look like:
First we have a go with
Future <Maybe Array>
:You can play around with
queryResult
andqueryResult2
. This should give you a good idea of what the Maybe monad can do.Note that in this case I am using Sanctuary, which is a purist version of Ramda, because of it's Maybe type, but you could use any Maybe type library and be happy with it, the idea of the code would be the same.
Now, let's add Either.
First let's focus on our transformation function, which I have modified a little:
So far so good. If the array obeys our conditions, we return the right branch of Either with the array, is not the left branch with an error.
Now, let's add this to our previous example, which will return a
Future <Maybe <Either <String, Array>>>
.So, what this tells us?
If we get an exception ( something not anticipated ) we print
The end is near!: ${err}
and we cleanly exit the app.If our DB returns nothing we print
[]
.If the DB does return something and that something is invalid, we print
"Invalid Greeting!"
.If the DB returns something decent, we print it!
Jesus Bananas, this is a lot!
Well, yeah. If you are starting with Maybe, Either and Flutures, you have a lot of concepts to learn and it's normal to feel overwhelmed.
I personally don't know any good and active Maybe / Either library for Ramda, ( perhaps you can try the Maybe / Result types from Folktale ? ) and that is why i used Sanctuary, a clone from Ramda that is more pure and integrates nicely with Fluture.
But if you need to start somewhere you can always check the community gitter chat and post questions. Reading the docs also helps a lot.
Hope it helps!