futurizer
Turn callback-style functions or promises into futures!!
This library is opinionated about its Future implementation. Internally, it uses Folktale data.task.
For an unopinionated library, please see futurize which is an excellent library, supports any compatible implementation of Future, and HEAVILY inspired this library.
Benefits of being opinionated are that this library ensures you are using a proven, production grade Future implementation, and makes import / require statements a breeze.
Please see simple examples below demonstrating usage. Futures are pure - for a great introduction, watch this video
Once you see the convenience and power of Futures it will redefine how you approach asynchronous functional programming (in JavaScript) for many situations.
This library integrates really nicely with functional libraries that are fantasy land compatible and dispatch functor, applicative functor, and monadic APIs
There is a summary of the essentials at the end of this readme if these concepts are new to you.
Futurize a callback-style function
Example 1
'use strict'; const future = futurizer; const helloAsync = { ; }; const hello = ; ;
Example 2
const fs = future = futurizer; read = ; const decode = { return buffer;}; ;
Futurize a promise
Example 1
// use your favourite Promise implementation, example using Qconst Q = const futureP = futurizerP; // promise returning functionconst helloPromise = { return Q;}; const hello = ; ;
API
futurizer :: CPS -> ( ...args -> Future )
futurizerP :: Promise -> ( ...args -> Future )
Frequently Asked Questions
Why should I bother? I like async with callbacks and promises..
Futures are alot like bluebird promises, and have the same ideas as promises, but nothing happens until you call fork!
If you defer calling fork, then you can add more functional pipelining simply by mapping over further behaviour. Also, because they use standardized functor, applicative functor and monadic functions, they can be seamlessly composed with functional libraries that understand those APIs. For instance, folktale, sanctuary and of course ramda all work really well together.
So use Futures only if you wish to make pure systems that are extendible, and founded on solid principles from mathematics / category theory.
Let me present more examples to give you better sense of usage scenarios:
const futureLast = ;//=> 1 { // async here}; //+ getJSON :: (JSON -> b) -> (Error -> c) -> String -> Object -> voidconst getJSON = ; //+ getJSON :: String -> Object -> Future(Error, JSON)const getJSON = ; //+posts :: Object -> Future(Error, [Post])const posts = //+ fiveLines :: String -> Stringconst fiveLines = //+ previewFile :: String -> Future(Error, String)const previewFile =
Control Flow
What about control flow (running several Future returning functions in a pipeline) and error handling?
Well, chain
can help here where the functions are promise returning.
See the functor, monaod survival guide at the end of this readme for further info.
Lets look at a couple of examples:
Successful chain
const R = chain = Rchain compose = Rcompose Task = ; //+ findUser :: Number -> Future(User)const findUser = { console; return Task;}; //+ getFriends :: User -> Future([User])const getFriends = { console; return Task;}; //+ renderTemplate :: [User] -> Future(String)const renderTemplate = { console; return Task;};
And we can call these functions as follows:
//+ friendPage :: Number -> Future(String)const friendPage = ; const errFn = console;const successFn = { console; to}; ; //=> findUser invoked with 1//=> getFriends invoked with {"id":1,"name":"harry"}//=> renderTemplate invoked with [{"id":2,"name":"bob"},{"id":3,"name":"tracy"}]//=> [{"id":2,"name":"bob"},{"id":3,"name":"tracy"}]
As you can see, first we called findUser, then passed the result of that into getFriends, and finally passed the result of that into renderTemplate to get our final result by forking our returned Future. And all of this happened inside Future returning functions!
What about errors?
In the above scenario, lets say we have an error in one of our functions as follows:
//+ getFriends :: User -> Future([User])const getFriendsError = { console; return Task;};
Then we carry out the same calls as before:
//+ friendPage :: Number -> Future(String)const friendPage = ; const errFn = { console; to;}; const successFn = { to; console;}; ; //=> findUser invoked with 1//=> getFriends invoked with {"id":1,"name":"harry"}//=> Something went wrong
Notice how the error gets propagated to the fork, and subsequent functions in the chain pipeline are not executed. Pretty cool hey!
What about Parallel execution?
If you are looking for something like Q.all then this is easily achieved by leveraging the Ramda traverse function.
;//=> Future([Post, Post])
This works something like Q.all but this one is much more general, much more principled. Because mapping array and Future same, we can commute future and array are two different functors and we commute them (flip the order).
What does this buy us? Well now we have one future and an array of reuslts, it is easy to know when the future has completed and can process the results. Contrast that with an array of Futures!
Functor, Applicative Functor, and Monad Survival Guide
Here is a sort of survival guide to getting started with functional programming and alegbraic data types.
Future is an algebraic data type, and hence it is instructive to understand the principles upon which it is based in order to better understand this library and how best to use it.
Here we use Ramda and Ramda-Fantasy - really illustrates simple usage of map
, ap
, and chain
as they can be used with Maybe
.
So here are the essentials to know (this focuses on usage of existing Monads in the ramda-fantasy package with ramda, and therefore does not indicate what rules need to be followed to roll your own monads etc):
const chai = expect = chaiexpect R = map = Rmap lift = Rlift identity = Ridentity chain = Rchain unnest = Runnest RF = Maybe = RFMaybe add = Radd; ;
Please feel free to contact me directly if you have any questions. Otherwise, please visit Ramda and join the ramda community on Gitter there too. There is always at least a handful of people there who are more than happy to discuss this subject!
License
MIT © arcseldon