Laissez-faire
A simple promise class
Getting Started
If your new to the idea of using promises in JS or just this particular implementation of then I recommend you start by downloading this repo and having a quick run through the koans. To do that run.
$ git clone https://github.com/jkroso/Laissez-faire.git && cd Laissez-faire
$ npm install
$ make koans
installing
With component
$ component install jkroso/Laissez-faire
With npm
$ npm install jkroso/Laissez-faire --save
then in your app:
var Promise =
see the API documentation for usage details
About promises
What's a javascript promise
A promise is an Object which boxes a value and provides an interface for accessing it (proxies). They, therefore, are similar in essence to javascript's variables though they don't benefit from any syntactic abstraction. The thing promises have over variables though is that they can provide access to values which don't yet exist in the machines local memory. Accessing a promise's value is always done by sending it a function. This way if the promise doesn't have direct access to its value it can simply store the function and call it when the value arrives. Of course if the promise already has access to its value then there is no need to do anything fancy at all and it can just call the function immediately. The point is the interface is consistent regardless of how long it takes for the value to arrive so we can get on with building abstractions.
Composition
If you read the paragraph above you may be left questioning the value of promises since everything I described can be done with simpler methods like continuation passing (CPS). If you implement an API using CPS you are saying I might not be able to deliver my result immediately so pass me a function and I promise to deliver my result to it as soon as I can. The problem with this approach though is that their is nothing left sitting around. The concept of a promise exists but only ephemerally inside the context of the callback. Promise's provide a way of concretising the concept and pulling it out of the hidden context of the callback. By pulling them out its much easier to compose them with other functions. Why hide work from yourself? Lets explore this problem by sequencing two operations which increment a number.
When done with synchronous functions capable of directly returning their result it looks like this:
{ return a + 1}
Now lets pretend a
isn't available right now and try deal with it using CPS.
{ // normally a delay here }
Its easy to do the first increment but how do we do the second. To do this we need access to the result of inc
but we can't return
the result from inc
because its called by getA
at some undefined time in the future. I have a few ideas:
- make
inc
take a callback
Nope. how would you then wire the firstinc
to call the second. This would rely ongetA
forwarding the second callback on toinc
. That gets messy fast and I've never seen a callback taking function implemented to do that. - extend
inc
Nope this breaks the black box abstraction that we wantinc
to be. - create a wrapper function
This is lame for similar reasons to the previous suggestion. We want to write logic not mechanics
Lets skip the CPS options though and see whats possible when you use promise objects instead of the limited scope promises callbacks provide.
First lets learn the API of promises which is just two methods. read
and write
. read
takes a function which it calls with the promises value as soon as it can. While write
is how you give a promise its value. Thats it. Now lets re-write getA
to return a promise object instead of taking a callback.
{ var p = // normally we aren't able to .write() immediately p return p}
This is all the knowledge of promise objects we need to implement our double inc
program declaratively.
var a = var b = var c = ab
Admittedly on the surface this looks like procedural code but thats because its mostly boilerplate which itself is in-fact procedural. What this code says is a
is some value. b
is the inc
of that and c
is the inc
of that. Nothing here manages the ordering and in fact it could be rearranged without affecting the result. Lets see if we can make the code read a bit more like it behaves though. Notice the pattern for creating a promise for the transformed value of another promise is:
- instantiate a promise object
- read from the parent promise
- write the transformed value to the new promise
We can use a function to abstract away this pattern.
{ var p = promise return p}
with when
the task of double incrementing a
becomes:
var a = var b = var c =
This is nice and declarative though in this example we don't really care about storing the intermediate values a
, b
, and c
. I just wrote it that way to keep the flow readable. It could just as readily be written as:
Which highlights a general readability issue when composing functions. Notice how to follow the flow of data in this last example you have to read the code backwards (right to left). Ideally the order our functions appear in our code and the order they execute would be the same. e.g:
And in JS that can be achieved by basically just plunking when
on to the promise object as a method. The only change you have to make to when
is to swap the promise
argument for this
. Its a shame really that we have to make such a superficial change in order to convert a function to a method but regardless its nice JS gives us the option to create methods.
So thats it I've introduced you to promises as objects and walked you through a very common abstraction used when working with them. Hopefully, you appreciate that promise objects aren't really abstractions themselves but instead the concretion of the concept of a promise which would otherwise be realised ephemerally and scoped within a single callback (if your were to use CPS). I like to think that by using promise objects in my code I am keeping all my cards on the table face up. With explicit promises adding/removing features from my programs is usually just a matter of adding and removing functions.
Further reading
There is a lot more to promises but I have chosen to stop here for fear of loosing the forest for the trees. Notably I have not talked about the story promises provide for handling errors.
links:
Just remember that at its heart a promise is just a more robust variable, thats were their value comes from. Its hard to see them for what they are when in JS at least they always come bundled with control flow abstractions.
Running the tests
$ npm install$ make
Then open your browser to the ./test
directory.
Note: these commands don't work on windows.