Brandon Martinez | brandonmartinez.com | @brandonmartinez
Skyline Technologies (We're Hiring!) | skylinetechnologies.com | @skylinetweets
In JavaScript, functions are considered first-class citizens; they are able to be passed as arguments or stored in variables or fields.
Callbacks are functions that are passed as an argument to another function.
function runThisFunction(myCallbackFunction) {
myCallbackFunction();
}
runThisFunction(function() {
console.log('Kind of useless, but I see your point.');
});
Here is a common example, passing an anonymous callback function to be used after the success of a jQuery AJAX request.
$.ajax({
url: '/myapi/',
type: 'POST',
data: { Id: 123, Description: 'Call me back, bro!' },
success: function(data) {
// Our callback function; this will be executed
// after the async query is finished
console.log(data);
myData.push(data);
}
});
That doesn't seem bad, does it? But what happens when things get more complex?
Have you ever written code that looks like this?
$.ajax({
url: '/myapi/',
type: 'POST',
data: { Id: 123, Description: 'Call me back, bro!' },
success: function(returnedData) {
// Success! Time to call our next service
$.ajax({
url: '/mynextapi/',
type: 'POST',
data: returnedData,
success: function(data) {
$('#messages').animate({
opacity: 0.25,
left: "+=50",
height: "toggle"
}, 5000, function() {
setTimeout(function() {
$(this).val('Finished!');
// How did we get here???
}, 1000);
});
}
});
}
});
Deep, intermingled heirachies of functions, often called asynchronously.
Scope is very difficult to track.
Code maintainability SUCKS!
Of course! Let's pull all of those anonymous functions into named functions. That will help, right?
function animateAfterFinalPosting(){
$('#messages').animate({
opacity: 0.25,
left: "+=50",
height: "toggle"
}, 5000, function() {
setTimeout(function() {
$(this).val('Finished!');
}, 1000);
});
}
function postInitialDataSuccess(returnedData) {
// Success! Time to call our next service
$.ajax({
url: '/mynextapi/',
type: 'POST',
data: returnedData,
success: animateAfterFinalPosting
});
}
function postInitialData(myData) {
$.ajax({
url: '/myapi/',
type: 'POST',
data: myData,
success: postInitialDataSuccess
});
}
postInitialData({
Id: 123,
Description: "Let's dive in!"
});
Better, right?
Code has become more manageable, however:
First, a discalaimer: Promises, or specifically the Promises/A+ standardization, are currently available in JavaScript's currently standardizing ECMAScript 6 additions. As such, they are not available in all browsers without using an additional library, like Q.js, or a polyfill.
For more information, I highly recommend reading the JavaScript Promises article at HTML5 Rocks (http://bmtn.us/1ikMPTc).
A promise is an abstraction over asynchronous functions and tasks in JavaScript.
At the minimum, a promise is a JavaScript object with a then function.
The then function returns a fullfilled value or throws an exception. The former allows you to chain multiple promises together (fluent-style).
Promises can have four states: fulfilled, rejected, pending, or settled.
A small sample of a promise chain. We're using anonymous functions here, but notice how much more readable the code is. Not only that, but it's linear!
// Using Q, basic concepts apply to Promises/A+
Q({
Id: 123,
Description: "Let's dive in!"
}).then(function(data) {
// jQuery isn't standard-compliant, wrap in a proper promise and return
return Q($.ajax({
url: '/myapi/',
type: 'POST',
data: data
}));
}).then(function(returnedData) {
return Q($.ajax({
url: '/mynextapi/',
type: 'POST',
data: returnedData
}));
}).then(function(nextReturnedData) {
$('#messages').animate({
opacity: 0.25,
left: "+=50",
height: "toggle"
}, 5000, function() {
setTimeout(function() {
$(this).val('Finished!');
}, 1000);
});
}).catch(function(error) {
// Easy error handling!
console.log(error);
});
Now let's move our anonymous functions into their named counterparts.
function sendFirstDataPost(data) {
// jQuery isn't standard-compliant, wrap in a proper promise and return
return Q($.ajax({
url: '/myapi/',
type: 'POST',
data: data
}));
}
function sendSecondDataPost(returnedData) {
return Q($.ajax({
url: '/mynextapi/',
type: 'POST',
data: returnedData
}));
}
// note that we don't _have_ to provide the
// previously returned data; javascript lets
// us ignore arguments in callbacks
function animateMessageBox() {
$('#messages').animate({
opacity: 0.25,
left: "+=50",
height: "toggle"
}, 5000, function() {
setTimeout(function() {
$(this).val('Finished!');
}, 1000);
});
}
function errorHandler(error) {
// Easy error handling! This method could
// potentially be reused in any promise chain
console.log(error);
}
// Using Q, basic concepts apply to Promises/A+
Q({
Id: 123,
Description: "Let's dive in!"
})
.then(sendFirstDataPost)
.then(sendSecondDataPost)
.then(animateMessageBox)
.catch(errorHandler);
We've been tasked with creating a lightweight web interface for retrieving and processing CommandSets from a web API:
Using anonymous callbacks, we'll send all commands to the web API and wait for a response.
Taking the previous example and breaking it into a promise-style chain, we send all commands to a web API and wait for a response.
In addition to the core specifications:
Using named callbacks to help clean-up the code from the first demo and add additional functionality.
In addition to the previous specifications:
Using the previous demo as a base, we've rewritten the logic to produce promise chains. Additionally, we show how easy it is to control the flow of child chains, as well as knowing when the entire process is finished.
While browsers continue to add and support the full Promises/A+ standard, you can still enjoy them in most ECMAScript 5 browers.
The JavaScript Promises article at HTML5 Rocks (http://bmtn.us/1ikMPTc) has a polyfill that can be used to support older browsers, as well (based on RSVP.js).
To see if your favorite browser supports the Promise standard, visit caniuse.com/promises.
The same as Demo 4, just using native Promises (no polyfill, so check your browser support!).
Brandon Martinez | brandonmartinez.com | @brandonmartinez
Skyline Technologies (We're Hiring!) | skylinetechnologies.com | @skylinetweets