Skip to main content

Featured

How to defeat that pesky bug

$.Deferred() - Promise functionality in ES5 with JQuery

Background


When writing javascript (predominantly ES5 and older) we rely on callbacks to allow async functions to run code once the async action has completed. This can get very messy, with callbacks calling other callbacks (usually defined as anonymous functions). There is a solution, jquery's Deferred function , which acts like Promises (not available to ES5 out of the box).



Callbacks:


Here is an example of a standard callback function. The 'doHomework' method accepts a function as a second parameter which is then called within the doHomework method, after other code has been ran.




You may be familiar with $.ajax requests. The success and error properties of the ajax() method are callback functions.




Callback HELL :


Our Saviour:

$. Deferred() is a factory that creates a new Deferred object. jQuery. Deferred is jQuery’s implementation of the 'Promise Pattern'.

It adds additional functionality to methods, makes code easier to read, and replaces the need for nested callbacks.

Deferred objects in jQuery represent a unit of work that will be completed later, typically asynchronously. Once the unit of work completes, the deferred object can be set to resolved or failed, then returned.

A promise is an object that may produce a single value some time in the future : either a resolved value, or a reason that it's not resolved (e.g., a network error occurred).

To Resolve or To Reject…



Deferred objects are resolved / rejected either automatically or manually.

Automatically:




$.ajax based methods e.g $.get(), and $.post() act upon the returned promise provided from the ajax method itself, based on whether the request was successful or not. If the method errors it will return "rejected" status, if successful the "resolved". As you can see in the image above, we call done() method and the fail() method, off the returned promise of the $.post() method. These will be called whether the $.post() method is successful or errored.



Manually:




Deferred objects can be resolved or rejected manually by creating the deferred object, and then calling the .resolve() method or the .reject() method, within the parts of the calling method, as seen in image above. This extra layer on control allows the developer to have total control of what conditions apply to which outcome.


Note: the deferred object can only be in one state at anyone time, and once resolved cannot be rejected.


Returning Promises from deferred object



The promise() method, on the Deferred object exists purely for encapsulation.
If you only return the pure Promise corresponding to that Deferred object, the caller can only read its state and attach callbacks. jQuery itself takes this approach, returning pure Promises from its AJAX methods. Pure promise objects can not be manipulated or changed, making them more code safe.




Attaching callbacks




Now we have the deferred object we can attach several methods to it.

.done(function) - This is called when the deferred object is resolved.

.fail(function) - This is called when the deferred object errors

.always(function) - This is called regardless of whether the deffered obect is successful or errors

.then(successMethod, failMethod, alwaysMethod) - This function can be used if you’d like to write all three methods shorthand
Useful uses of $. Deferred()



Multiple Callback Definitions:

$.Deferred() also gives you the ability to configure multiple outcomes and methods to be ran such as .always(), .done(), .fail() based on the state of the deferred object.


Run multiple functions:
You can register multiple functions to done() or fail(), so that multiple methods are ran on success or failure. These will be ran in order of registration.


Get a progress report:
Deferred objects have a state which is updated based on which method is called i.e resolve or reject. There is also a method of .notify() which gives the concept of progress towards the resolved state. You could then attach a callback to the .progress() method.
Real life scenario : a progress bar, after loading e.g a file in list of files, call the notify() method with a count, and have the progress method spit out updated value.


Example of progress report:







Async await functionality


Problem:


You need to make 2 async requests for data, and then wait for both async requests to finish to use result from both methods combined.

Solution:

Option 1 

Nested callbacks - looks horrible and get can get very messy

Option 2

$.when() – awaits both functions to finish and then combines promises from both methods, returning both return values to utilise.




Option 1:



Option 2:



Note: .when() combines the promises from both calls, meaning return values can be different depending on number of return values.

.when() Method 1 - return values from async methods are single values, therefore val1 is value, val2 = another value

.when() Method 2 - return values from the async methods are multiple return values, therefore returns multiple as an array.



Reusable code

Deferred objects can make your code reusable and help and make it easier to change.

Example:

You wish to change what happens on success in one of the scenarious, your options are:

Left- Create a second method, so have one for users, and one for actions, and set the success and error callbacks to do different things.

Right - 1 ajax method, adapt it to take in a url and data object, then utilise the $.Deferred() to inform the program to reject() or resolve(), then return the promise. This method is then reusable , and the calling method determines the next steps upon receiving the data. (Recommended Option)







There you have it, a brief summary and use cases of $.Deferred and how to accomplish Promise functionality within ES5 using jQuery.


Feel free to follow me on Twitter @gweaths, and subscribe to the blog.



Comments

Popular Posts