A Little More About Promises

Thursday, May 8th 2014

Yesterday, I wrote about Promises in JavaScript with rsvp. I want to spend a little more time with examples of promises to prepare you for my review of leslie-mvp.

Chaining promises with promise return values

Imagine that you want to write an application that, to render a Web page, needs to make three queries of the database. I will stick with nano and CouchDB for the example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var nano, db;
nano = require('nano');
db = nano('http://localhost:5984/callback_example');

function errorResponse(response, code, body, mime) {
mime = mime || 'text/html';
response.writeHead(code, {
'Content-Type': mime
});
response.end(body);
}

exports.index = function (request, response) {
var personId = request.param('personId');

db.get(personId, function (e, person) {
if (e) {
return errorResponse(res, 404, 'Not Found');
}

db.view('people', 'names', function (err, peopleNames) {
if (err) {
return errorResponse(res, 404, 'Not Found');
}

db.view('companies', 'names', function (error, companyNames) {
if (err) {
return errorResponse(res, 500, 'Server can find no companies');
}

res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
person: person,
peopleNames: peopleNames,
companyNames: companyNames
}));
});
});
});
}

I don’t think I need to explain this code in too much detail. I just want you to see that these three database calls get nested within one another. While I can now read this kind of code pretty easily, it took me a while before my organic parser got to that point.

Now, I present the same code using promises with rsvp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var nano, db, rsvp, get, view;
rsvp = require('rsvp');
nano = require('nano');
db = nano('http://localhost:5984/callback_example');
get = rsvp.denodeify(db.get);
view = rsvp.denodeify(db.view);

exports.index = function (request, response) {
var error, personId, returnValue;
personId = request.param('personId');

error = { code: 404, msg: 'Not Found' };
get(personId)
.then(function (person) {
returnValue.person = person;
return view('people', 'names');
})
.then(function (peopleNames) {
returnValue.peopleNames = peopleNames;
error = { code: 500, msg: 'Server can find no companies' };
return view('companies', 'names');
})
.then(function (companyNames) {
returnValue.companyNames = companyNames;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(returnValue));
})
.catch(function () {
res.writeHead(error.code { 'Content-Type': 'text/html' });
res.end(error.msg);
});
}

The code may “save” 20% of the lines but that does not do it justice. This reads so much nicer, makes so much more sense to our linear minds. It also provides a single place where we handle errors as opposed to the previoius example where each nested function starts with an if (e) { check.

I want you to notice the important part to this example seen on lines 16 and 21. Each of the functions in the then clauses returns another promise. This keeps the promise chain going so that the next then gets the resolved value for the last return-ed promise. In this way, we can chain multiple asynchronous calls in a seemingly synchronous way.

Chaining promises with a multi-promise return value

That in of itself is pretty cool. However, we can do something even more clever. rsvp allows us to return objects with properties that are promises and it will do all of them for us and return the value in a similar object. It also gives us a convenience method to invoke a group of promises.

For example, imagine that you have an object

var promises = {
  first: «a promise»,
  second: «a promise»,
  third: «a promise»
};

then rsvp will let you invoke rsvp.hash(promises). The then method will then receive an argument to its method that contains the same property names and the results of the promises, like this

{
  first: «result of promise»,
  second: «result of promise»,
  third: «result of promise»
}

That means we can simplify the use of the promises in the code from the last section to just this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var nano, db, rsvp, get, view;
rsvp = require('rsvp');
nano = require('nano');
db = nano('http://localhost:5984/callback_example');
get = rsvp.denodeify(db.get);
view = rsvp.denodeify(db.view);

exports.index = function (request, response) {
var personId, promises;
personId = request.param('personId');
promises = {
person: get(personId),
peopleNames: view('people', 'names'),
companyNames: view('companies', 'names')
};

rsvp.hash(promises)
.then(function (values) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(values));
})
.catch(function () {
res.writeHead(404 { 'Content-Type': 'text/html' });
res.end('Not found');
});
}

And, that meets my standard for nice software:

  • concise
  • transparent
  • elegant

Why are you still talking about this, Curtis?

I want to introduce leslie-mvp to you and witout this understanding of promises, the implementation will make little to no sense. I think that, given yesterday’s post and this one, you should understand promises well enough to follow along in the design and code of leslie-mvp.

See y’all tomorrow!