/

Deferreds and Promises: Structuring Your JavaScript Code with Ease (+ Ember.js Example)

Deferreds and Promises: Structuring Your JavaScript Code with Ease (+ Ember.js Example)

tags: [“Promises”, “Deferred”, “JavaScript”, “Async Management”, “Ember.js”]

Promises are an innovative approach to managing asynchronous code in JavaScript. They provide a structured way to handle events and make your code more readable. In this blog post, we will explore the concept of Promises and Deferreds in JavaScript, using examples with jQuery and Ember.js.

What are Promises?

A Promise is an object that represents an event and its lifecycle. It starts in a pending state when it is called and transitions to a resolved or rejected state when the event is completed. In some cases, a Promise may stay in the pending state indefinitely and never resolve.

Promises are a new way of handling JavaScript events and offer a more readable and less quirky alternative. Currently, there are two main implementations of Promises in JavaScript: libraries that follow the Promises/A specification, and jQuery.

Introducing jQuery Promises

Let’s start by looking at the jQuery implementation of Promises, as it is widely used and readily available. jQuery provides an object called Deferred, which is essentially a Promise with additional methods to trigger its resolution or rejection. A Promise, on the other hand, is a read-only component of a Deferred.

Here is an example of creating a Promise using jQuery:

1
var promise = $('div.alert').fadeIn().promise();

You can now add .done() and .fail() callbacks to handle the resolution or rejection of the Promise. Promises for animations are commonly used in jQuery 1.8, often with callbacks for progress.

Another example of using a Promise is with an AJAX call:

1
2
var promise = $.get(url);
promise.done(function(data) {});

A Deferred is created by the developer, and callbacks are added to it. The Deferred can be resolved using the .resolve() method, like this:

1
2
3
var deferred = new $.Deferred();
deferred.done(function(data) { console.log(data) });
deferred.resolve('some data');

The state of a Deferred can be changed using the .resolve() or .reject() methods. Once a Deferred is resolved or rejected, its state cannot be changed.

Callbacks can be attached to a Promise using the following methods:

1
2
3
.done() // to be executed when the Promise is successfully resolved
.fail() // to be executed when the Promise is rejected
.always() // to be executed in either case

These callbacks can also be combined using the .then() method:

1
promise.then(doneFunc, failFunc, alwaysFunc);

These are just the basics of the jQuery implementation of Promises and Deferreds. Now, let’s look at some real-world examples.

jQuery Examples

Here are some examples of using Promises and Deferreds in jQuery:

  1. Executing a function and calling a callback when it’s finished:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$.when(execution()).then(executionDone);

function execution(data) {
var dfd = $.Deferred();
console.log('start execution');

// In the real world, this could be an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);

return dfd.promise();
}

function executionDone(){
console.log('execution ended');
}
  1. Processing elements in an array and calling a callback when all of them are completed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var data = [1,2,3,4]; // ids coming back from serviceA
var processItemsDeferred = [];

for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}

$.when.apply($, processItemsDeferred).then(everythingDone);

function processItem(data) {
var dfd = $.Deferred();
console.log('called processItem');

// In the real world, this could be an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);

return dfd.promise();
}

function everythingDone(){
console.log('processed all items');
}
  1. A more complex example, fetching elements from an external resource and processing them:
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 data = []; // ids coming back from serviceA
var fetchItemIdsDeferred = fetchItemIds(data); // adds the ids to data

function fetchItemIds(data){
var dfd = $.Deferred();
console.log('calling fetchItemIds');

data.push(1);
data.push(2);
data.push(3);
data.push(4);

setTimeout(function() { dfd.resolve() }, 1000);
return dfd.promise();
}

fetchItemIdsDeferred.done(function() {
var processItemsDeferred = [];

for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}

$.when.apply($, processItemsDeferred).then(everythingDone);
});


function processItem(data) {
var dfd = $.Deferred();
console.log('called processItem');

// In the real world, this could be an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);

return dfd.promise();
}

function everythingDone(){
console.log('processed all items');
}

These examples demonstrate how Deferreds can be used to process a for loop and wait for the completion of all tasks before executing further code.

Ember.js Example

I often use Deferreds in combination with Ember.js. Here is an example of integrating Deferreds with Ember.js:

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
App.DeferredHelper = {
arrayContainsElements: function(arrayName, object) {
var dfd = $.Deferred();
if (!object) object = App;

var interval = setInterval(function() {
if (object.get(arrayName).length > 0) {
clearInterval(interval);
dfd.resolve();
}
}, 50);

return dfd.promise();
},

variableIsSet: function(variableName, object) {
var dfd = $.Deferred();
if (!object) object = App;

var interval = setInterval(function() {
if (object.get(variableName) !== undefined) {
clearInterval(interval);
dfd.resolve();
}
}, 50);

return dfd.promise();
}
};

With Ember.js, you can use the DeferredHelper object and its methods to check if an array has elements or if a variable is set:

1
2
3
4
$.when(App.DeferredHelper.arrayContainsElements('itemsController.content'))
.then(function() {
// Do something
});
1
2
3
4
5
6
7
8
9
10
11
$.when(App.DeferredHelper.variableIsSet('aVariable'))
.then(function() {
// Do something
});

//&

$.when(App.DeferredHelper.variableIsSet('aVariable', anObject))
.then(function() {
// Do something
});

These examples demonstrate how Deferreds can be used in conjunction with Ember.js to handle asynchronous operations.

Other Libraries and Implementations

While we have focused on the jQuery implementation, there are other libraries available that provide Promises and Deferreds functionality, such as Q, RSVP.js, and when.js. These libraries have their own specialized implementations of Promises and Deferreds, offering similar functionalities.

Here is an example using the when.js library to process AJAX calls and handle the results using Promises:

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
function processItem(item) {
var deferred = when.defer();

$.ajax({
url: '/api/itemDetails',
type: 'GET',
data: {
item: item
},
success: function(response) {
deferred.resolve(JSON.parse(response));
},
error: function() {
deferred.reject('error');
}
});

return deferred.promise;
}

var item = {
id: 1
};

processItem(item).then(
function gotIt(itemDetail) {
console.log(itemDetail);
},
function doh(err) {
console.error(err);
}
);

Conclusion

Promises and Deferreds provide a structured approach to managing asynchronous code in JavaScript. They offer a more readable and less error-prone way of handling events and structuring your code. Whether you choose to use the jQuery implementation or explore other libraries, Promises and Deferreds are powerful tools that can greatly improve the organization and readability of your JavaScript code.