function getUserInfo(id, callback) {
$.ajax({
url: "/user/" + id,
type: 'GET',
dataType: 'html',
success: function(data, textStatus, xhr) {
callback(null, data);
},
error: function(xhr, textStatus, errorThrown) {
callback(new Error(textStatus), errorThrown);
}
});
}
function getUserComments(id, callback) {
$.ajax({
url: "/user/" + id + "/comments",
type: 'GET',
dataType: 'html',
success: function(data, textStatus, xhr) {
callback(null, data);
},
error: function(xhr, textStatus, errorThrown) {
callback(new Error(textStatus), errorThrown);
}
});
}
// Get the user info
getUserInfo(1, function(err, userDoc) {
if (!err) {
// get the user comments
getUserComments(1, function(err, commentArray) {
if (!err) {
DisplayUserInfoWithComments(userDoc, commentArray);
} else {
// Do something to handle errors
}
});
} else {
// Do something to handle errors
}
}
// Get the user info
var userDoc = null;
var commentArray = null;
var wasError = false
getUserInfo(1, function(err, inDoc) {
if (!err) {
userDoc = inDoc;
if (commentArray) {
DisplayUserInfoWithComments(userDoc, commentArray);
}
} else {
wasError = err;
wasError.errThrown = arguments[1];
}
});
getUserComments(1, function(err, inArray) {
if (!err) {
commentArray = inArray;
if (userDoc) {
DisplayUserInfoWithComments(userDoc, commentArray);
}
} else {
wasError = err;
wasError.errThrown = arguments[1];
}
});
// Deferred method
function getUserInfo(id) {
return $.ajax({
url: "/user/" + id,
type: 'GET',
dataType: 'html',
}).then(function(data, textStatus, xhr) { return data; });
}
function getUserComments(id, callback) {
$.ajax({
url: "/user/" + id + "/comments",
type: 'GET',
dataType: 'html'
}).then(function(data, textStatus, xhr) { return data; });
}
// Get the user info; deferred method
var userDfd = getUserInfo(1);
var commentsDfd = getUserComments(1);
DisplayUserInfoWithComments(userDfd, commentsDfd);
function DisplayUserInfoWithComments(userDfd, commentsDfd) {
$.when(userDfd, commentsDfd).then(function(user, comments) {
// Previous contents of DisplayUserInfoWithComments in here
}, function(err) {
// Handle the error
});
}
Some code is just "glue" -- it calls other functions and then passes values where they need to go.
Some code is deeply involved with the data.
Some code knows what should happen when there is an error, and some code does not.
Promises give us an easy way to put the responsibility in the correct part of the code, but still pass the data through the other parts.
Express REST API made easy with promises
from bin/make_fake_data
function cleanDatabase() {
var waitFor = [];
console.log("Cleaning database");
waitFor.push(dropCollection('users'));
waitFor.push(dropCollection('comments'));
return Q.all(waitFor);
}
from bin/make_fake_data
var doneDfd = Q(null); // Initial resolved promise
doneDfd = doneDfd.then(cleanDatabase());
// create users
var userDfd, commentsDfd;
for (var i = 0; i < 20; ++i) {
// Note that since this is sequential, we wait on doneDfd
userDfd = doneDfd.then(createRandomUser);
commentsDfd = addCommentsForUser(userDfd);
// The result Promise for this iteration is then stored back in doneDfd
doneDfd = Q.all([userDfd, commentsDfd]);
}
doneDfd.then(function() {
console.log("All done!");
}, function(err) {
console.error("Everything has broken with the error: ", err);
});
This is certainly not as clean as a synchronous language -- but can you imagine doing all of this sequentially using callbacks?from bin/make_fake_data
function addCommentsForUser(userDfd) {
// Before we can add the comments, we need userDfd to resolve first.
// It is possible it may already be resolved, so we use Q.when which
// will make it a promise -- if it's a value, the promise will be immediately
// resolved.
return Q.when(userDfd).then(function(user) {
var savedObjects = [];
var comment;
for (var i = 0; i < 10; ++i) {
comment = new Comment();
comment.user = user;
comment.text = "I " + randgen(verbs) + " " + randgen(first_names);
savedObjects.push(Q.nbind(comment.save, comment)());
}
return Q.all(savedObjects);
});
}
Note that userDfd (dfd is a prefix I use, short for "Deferred", another name for a Promise) might or might not be a Promise. If it is a resolved User object this will work exactly the same.an npm module that understands promises
https://github.com/taxilian-promises/sendresponse
sendResponse was adapted from a library we use at GradeCam
This code comes from express-demo in routes/rest.jsrouter.get('/users', function(req, res) {
var userList = User.find().exec();
res.sendResponse(userList);
});
router.get('/users/:email', function(req, res) {
var user = getUserByEmail(req.params.email);
res.sendResponse(user);
});
router.get('/users/:email/comments', function(req, res) {
var userDfd = getUserByEmail(req.params.email);
var comments = getCommentsForUser(userDfd);
res.sendResponse(comments);
});
// Wrong way!
function getCoolStuff(someValue) {
var deferred = Q.defer();
setTimeout(function() {
if (Something_cool_happened) {
deferred.resolve(some_cool_value);
}
}, 500);
}
// Right way!
function getCoolStuff(someValue) {
var deferred = Q.defer();
setTimeout(function() {
if (Something_cool_happened) {
deferred.resolve(some_cool_value);
} else {
deferred.reject(new Error("Nothing cool happened"));
}
}, 500);
}
// Wrong way!
GetDBRecord().then(function(result) {
// Do something cool with the result
});
// Right way!
GetDBRecord().then(function(result) {
// Do something cool with the result
}, function(err) {
// Panic!
});
Promises are generally pretty efficient, but there are things to keep in mind when using them:
- Excessive chaining can cause excessive stack depth
- While a promise exists, its resolution value exists
- This can lead to memory leaks!
- Even the small overhead of the function calls and event bus on a promise can add up if enough calls are made
There are times when the result of a promise is less important than whether or not the promise has settled -- or even whether it succeeded or failed!
function displayLoadingIndicator(promise) { showLoadingIndicator(); var hide = function() { hideLoadingIndicator(); }; Promise.when(promise).then(hide, hide); }
Some libraries, such as jQuery Deferred, have a .always() shortcut for this
function changeTheUser(user) {
return Promise.when(user).then(function(user) {
// The user is resolved!
});
}
The ECMAScript 6 standard seems to be Promises/A+ compliant.