In jQuery, deferred objects represent async operations. A deferred object is like a promise whose resolve and reject functions are exposed as methods. Example 4-17 shows a loadImage function using a deferred object.
Example 4-17. Simple deferred object in jQuery
function loadImage(url) {
var deferred = jQuery.Deferred();
var img = new Image();
img.src = url;
img.onload = function () { deferred.resolve(img);
};
img.onerror = function (e) { deferred.reject(e);
};
return deferred;
}
The standard Promise API encapsulates the resolve and reject functions inside the promise. For example, if you have a promise object p, you cannot call p.resolve() or p.reject() because those functions are not attached to p. Any code that receives a reference to p can attach callbacks using p.then() or p.catch() but the code cannot control whether p gets fulfilled or rejected.
By encapsulating the resolve and reject functions inside the promise you can confi‐
dently expose the promise to other pieces of code while remaining certain the code cannot affect the fate of the promise. Without this guarantee you would have to con‐
sider all code that a promise was exposed to anytime a promise was resolved or rejec‐
ted in an unexpected way.
Using deferreds does not mean you have to expose the resolve and reject methods everywhere. The deferred object also exposes a promise that can be given to any code that should not be calling resolve or reject.
Compare the two functions in Example 4-18. The first is a revised version of load Image that returns deferred.promise() and the second is the equivalent function implemented with a standard promise.
Promises in jQuery | 51
Example 4-18. Deferred throws synchronous errors
function loadImage(url) {
var deferred = jQuery.Deferred();
// ...
return deferred.promise();
}
function loadImageWithoutDeferred(url) {
return new Promise(function resolver(resolve, reject) { var image = new Image();
image.src = url;
image.onload = function () { resolve(image);
};
image.onerror = reject;
});
}
The main difference between the two functions is that the function used as a deferred could throw a synchronous error while any errors thrown inside the function with the Promise constructor are caught and used to reject the promise. Promises created from jQuery deferreds do not conform to the standard ES6 Promise API or behavior.
Some method names on jQuery promises differ from the spec; for example, [jQuery Promise].fail() is the counterpart to [standardPromise].catch().
A more important difference is in handling errors in the onFulfilled and onRejected callbacks. Standard promises automatically catch any errors thrown in these callbacks and convert them into rejections. In jQuery promises, these errors bubble up the call stack as uncaught exceptions.
Also, jQuery will invoke an onFulfilled or onRejected callback synchronously if settling a promise before the callback is registered. This creates the problems with multiple execution paths described in Chapter 1.
For more differences between standard promises and the ones jQuery provides, refer to a document written by Kris Kowal titled Coming from jQuery.
Some developers may prefer the style of deferred objects or find them easier to understand. However, a more significant case for using a deferred is in a situation where you cannot resolve the promise in the place it is created.
Suppose you are using a web worker to perform long-running tasks. You can use promises to represent the outcome of the tasks. The code that receives the response from the web worker will resolve the promise so it needs access to the appropriate resolve and reject functions. Example 4-19 demonstrates this.
Example 4-19. Managing web worker results with deferred objects
var deferred = jQuery.Deferred();
deferreds[id] = deferred; // Store deferred for later resolution console.log('Sending task to worker: ' + task);
worker.postMessage({
id: id, task: task });
return deferred.promise(); // Only expose promise to calling code }
background('Solve for x').then(function (result) { console.log('The outcome is... ' + result);
}).fail(function(err) {
console.log('Unable to complete task');
console.log(err);
});
// Console output:
// Sending task to worker: Solve for x // The outcome is... some calculated result
Example 4-19 shows the contents of two files: tasks.js for the web worker and main.js for the script that launches the worker and receives the results. The worker script is extremely simple for this example. Any time it receives a message it replies with an object containing the id of the original request and a hard-coded result. The background function in the main script returns a resolved promise once the worker sends a “completed” message for that task. Since processing the completed message
Promises in jQuery | 53
occurs outside the background function that creates the promise, a deferred object is used to expose a resolve function to the onCompleted callback.
For detailed information on web workers, refer to Web Workers:
Multithreaded Programs in JavaScript by Ido Green (O’Reilly.)
One final note for this section: if you wish to use a deferred object without jQuery, it is easy to create one using the standard Promise API, as shown in Example 4-20.
Example 4-20. Creating a deferred object using a standard Promise constructor
function Deferred() { var me = this;
me.promise = new Promise(function (resolve, reject) { me.resolve = resolve;
me.reject = reject;
});
}
var d = new Deferred();
Summary
Libraries offer an extended set of features for working with promises. Many of these are convenience functions that save you from mixing the plumbing with your code.
This chapter covered a number of features that the Bluebird library provides, although Q and other libraries offer similar functionality and are also popular among developers. It also explained the deferred objects jQuery uses. These objects expose promises that have some significant behavioral differences compared to the ES6 standard.