Futures in TypeScript
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
flatMap
the 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.
Adding Types
This is typescript after all. Let’s add some types.
Adding Map
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 T
to 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 chain
and apply
but the basics are done.
With Both Reject and Resolve
Let’s try to do a version that supports both resolve
and reject
. This is a bit more complicated but not much
let’s start with the interface. And for convenience sake will do of
and reject
. 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.
The 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.
Adding map
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.
Does our of
and reject
functions work. Note the dummy function that is passed. It is somethig like a Identity function. Makes this simplistic implementation work
Conclussion
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.