Promises in JavaScript with rsvp
JavaScript with its single-threaded execution encourages the use of asynchronous programming and callbacks that can lead to some really tough-to-manage code. The use of promises can radically simplify the structure of our asynchronous JavaScript code which leads to more comprehensible code. This post talks about the use of promises in JavaScript, specifically with the rsvp library.
In case you’re interested, our good friend Bryan Ray has his own JavaScript-oriented post in reference to his awesome side project Huddle, a “node-based chat application that composes many useful features for helping small teams work more effectively.”
The problem
I’m going to use an example for node.js; however, you can apply the same techniques for browser-based code for your favorite browser[1].
The following example loads a file, parses the JSON in it, and uses nano to save each “record” to an instance of a CouchDB.
1 | var fs, nano, db, path, filepath; |
The first six lines just set up the stuff we need: boring.
Line 8 declares the function uploadToCouch
that I use to actually insert the
document into the database. The list
parameter contains the list of things to
insert into the database. The e
parameter contains an error from the last
upload, if any.
Line 9 checks for an error and quits if it finds one.
Line 12 terminates the uploads since the upload list is empty.
Line 15 inserts the first item in the list into CouchDB and sets the callback
for the insert function to uploadToCouch
with the “tail” of the list, that is,
the list with the first item removed since that’s the thing we’re uploading into
CouchDB in that call.
You may have not used (or even seen) the bind
method available in JavaScript
5.1 and newer. The line
1 | uploadToCouch.bind(null, list.slice(1)) |
is the same as this
1 | function (list) { |
Anyway, if you don’t program with functional languages that promote tail recursion modulo cons, then the code in the block above may make no sense to you and many others. We need a better way to express this.
Using an asynchronous utility library
Now, I’m going to rewrite the previous code block using an asynchronous utility library. I have chosen async for this example; however, you can find a lot of others out there, too.
1 | var fs, nano, db, path, filepath, async; |
This seems much simpler and easier to understand, in my opinion.
Line 16 marks the first real deviation from the last section where the code
iterates over each of the records found in the file and pushes a function
into
the inserts
array.
Line 21 uses the async library to invoke all of the insert functions in parallel and, should something bad happen, prints out the error statement from the offending insert.
Promises, though, offer us something even more comprehensible.
Using promises
Before we jump into the actual example with promises, let’s look at the way the community has decided to express promises with JavaScript.
Promises/A+
From the Promises/A+ Web site:
A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.
In short, a promise is an object that has a then
method that takes two
parameters of type Function
. The promise should invoke the function in the
first parameter if everything goes well. The promise should invoke the second
function if something failed.
Here’s a practical example using rsvp:
1 | var fs, rsvp; |
The promise here accepts a function
with the resolve
and reject
parameters. When the code successfully reads the file, it “resolves” the promise
with the data read in from the file. If the read operation fails, then the code
“rejects” the promise and provides the error as the reason for that rejection.
Here’s the cool thing: if your code resolves a promise with another promise, the promise specification that the promise library must evaluate that promise, too! This means that the promise specification has chaining of promises built in. We can use that to our benefit!
The example with promises
Here I go with the rsvp library to do the same thing.
1 | var fs, nano, db, path, filepath, rsvp, insert; |
On line 8, you can see a call to rsvp.denodeify
. This takes a function with a
normal asynchronous callback signature of
function ([data], function callback(error, result))
and turns it into a
promise. This call to denodeify turns the insert method of nano into a
promsie for later on.
Line 15 creates a “default promise” that resolves rather than rejects. That will put the promise into a state of “continue”.
Line 18 shows that for each record, we just chain a call to insert
for the
record onto the promise.
Line 20 catches any error from the promise chain’s execution and just prints it out to standard error.
I think this is the easiest to understand of the three. No tail recursion, no
array of function invocations, just repeated calls to then
. I really like
that.
Where does that leave us?
Now that you understand promises (or have started on that journey), I can now write about leslie-mvp. It turns out that these promises make it really easy to write complex frameworks in a straight-forward and easy-to-understand way. Take a look at the source code and you’ll see promises everywhere. They provide the backbone of the evaluation of the entire MVP stack!
More on that in a future post, though!