In functional programming Promises are not the norm for async communications. The datatype Future is far more wide spread. This will be my try to implement a basic Future in TypeScript.
What Differs Futures from Promises
In all honesty not very much differs from promises. The main thing is that a Promise is evaluated when it is created while a Future is evaluated when you explicitly call
fork. Further they adhere to the monadic interface, something that Promises does not do.
This is what it would look like using Futures
The promise version uses
then for everything but the Future version have three distinct functions.
- Map - Converts the data from one form to another.
- Chain - This takes a future and flattens it, absorbing it into the Future chain. Just like
flatMapthe Promise version also does this but it is far from obvious and is handeled automatically.
- Fork - Explicitly evaluate the future.
A Future cannot be evaluated if the continuations (the functions provided to
fork) are not provided. A Promise will run even if no continuations exists. Also when a Future is created you have the option to call
fork as many times as you like. Every new fork will result in another evaluation. This is not possible using Promises.
A Simple Example
Let’s get started!
This creates a higher order function that takes the function
f as a parameter returning a new
simpleFuture with a
fork function on it. Calling the fork function with a handler will result in the future being evaluated. This requires that the function
f is a function that takes one callback parameter (hard to know without types). Something like this:
Wow great! What is happening.
simpleGetHttp is called and
console.log is passed as the
res parameter. When the timeout is finished
console.log will be called with the result. Pretty much what we want.
This is typescript after all. Let’s add some types.
This is all great but we need a map function too (in reality we need much more but let’s start with map)
Fist the interface
map will be a generic function taking function parameter that takes a value of type
T and mapping that to a value of type
U. Then this value will be wrapped in a new
ISimpleFuture of type
U. What would the implementation for this interface look like?
That is a bit more complicated. For starters is a function converting a value from
U (parsing a string to a number for example). What is returned is a new
simpleFuture with a whole lot of wrapping going on. Let’s take it for a spin
That works. Sheer luck with all that wrapping. To complete the monadic interface a couple of other functions such as
apply but the basics are done.
With Both Reject and Resolve
Let’s try to do a version that supports both
reject. This is a bit more complicated but not much
let’s start with the interface. And for convenience sake will do
of creates and resolves a Future in one go while
reject creates and rejects it.
Since we have both a reject and a resolve function the generic interface needs two type variables. The
reject handler can be passed an
error object while the
resolve can be passed a string.
This is a sample implementation
First the function expected to be passed to the constructor now takes two arguments one function that takes a
T, the reject type and a function that takes
U the resolve type. In essence the whole thing works as before just using two variables.
of function takes a
U and returns a new future that calls the
resolve function with that value.
reject works the same way but calls the reject function. As simple as that.
Let’s test it.
This one needs a
map function as well.
this leads to the implementation
If you managed to understand the simple version this will pretty much be the same. The only thing worth mentioning is that
map will only map success values. Better implementations of Future will have a special
bimap function that can map both rejections and resolved values.
reject functions work. Note the dummy function that is passed. It is somethig like a Identity function. Makes this simplistic implementation work
There is no mystery behind the Future data type. It is pretty much the same thing as a Promise. The upside is that it is lazy and I at least thinks it is easier to work with with sane names for the methods and less magic then promises.
Whatever you do do not use any of this code instead take a look at the following libraries.