Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

jQuery Promise: An introduction to AJAX with multicast delegates

5.00/5 (3 votes)
13 Aug 2013CPOL4 min read 13.6K  
An introduction to AJAX with multicast delegates.

AJAX presents a fantastic way to make the applications responsive, especially when there multiple interactive elements on todays websites, or better named as "Single Page Applications". These applications contain their logic behind a single main page (some helper child pages or controls apart). All the interaction is focused within the single page. Take GMail or FaceBook for example.

Such pages are highly dependent upon immediate reaction whenever a backend process is completed, and updating several areas in the user interface as a result of a single interaction. Now that's simple, anyone can say. Have couple of functions to update the UI element's state, and call them in the success handler of the AJAX call.

JavaScript
1:      $.ajax({  
2:        type: "POST",  
3:        url: webmethod,  
4:        data: param,  
5:        contentType: "application/json; charset=utf-8",  
6:        dataType: "json",  
7:        success: function (msg) {  
8:          Function1();   Function2(); // Add as many functionality as requred
9:        },  
10:        error: function (err) {  
11:          alert("Error: " + err.responseText);  
12:        }  
13:      });

Now we have been doing this way for long, and its been working for us. So what's new or different we can (or should) do? The answer is, although this works fine, nothing wrong in technical. However, the design presents a tight coupling. All applications need to have a clear "Separation of Concerns", wherein each portion does only its own responsibility. In this example, however, we have tied up the jQuery's AJAX function call to its result functionality. Ideally, the function call should be just a boilerplate code, where the user can pass the webmethod name and the parameter list, and leave the rest to implementation.

JavaScript
1:        function AjaxWrapper(webMethodName, parameterListInJsonFormat) {  
2:          $.ajax({  
3:            url: "Service.svc/" + webMethodName,  
4:            contentType: "application/json; charset=utf-8",  
5:            type: "POST",  
6:            input: parameterListInJsonFormat  
7:          });  
8:          // other code...  
9:        }

This wrapper will work for all calls, the caller just needs to tell which method and what input are to be given.

What this wrapper lacks is a way to put success and failure handlers. Right now, this is a pure one-way call, with no care given to the result!

Now within jQuery, we have the concept of "promise" which fulfills this requirement automatically. "Promise" is a concept which guarantees that the attached handler will get called upon the configured state change.

jQuery has a "$.Deferred" object, which bounds to an async call's result. The "deferred" concept is literal, the timing of processing of an async call is non-deterministic; it is postponed (or deferred) to future.

jQuery returns a promise from its $.ajax( { } ); function. The promise object can be used to bind handlers to various events which can occur when doing an asynchronous call. The 'promise' is that the handler will always get called once the call completes with the given event.

When a promise is first created by returning from an AJAX call, it is in a "pending" state. This means that the call has been made, however the service is still performing its task. Once the service has completed, the promise  resolves and the task is done. In case of any exceptions in service, the promise is rejected with the task getting failed. Whatever happens, something always happens to the promise.

Here, "done", "fail" and "always" are the events for the promise, and handlers are attached to them.

Let's illustrate with an example. We create simple and steady and historical Hello World service, where one method each return "Hello" and other "World", just to simulate two separate but dependent tasks. We also create one method which throws an explicit exception, to illustrate service failure. (Although the code is in C#.NET, the same service can be written in any language of choice)

 1:      [WebMethod]
 2:      public static string ReturnHello()
 3:      {
 4:          // Simulate a long running service
 5:          Thread.Sleep(5000);
 6:          // now the task is over, return the output
 7:          return "Hello";
 8:      }
 9:
10:      [WebMethod]
11:      public static string ReturnWorld()
12:      {
13:          Thread.Sleep(10000);
14:          return "World";
15:      }
16:
17:      [WebMethod]
18:      public static string ReturnException()
19:      {
20:          throw new Exception("There is some error");
21:      }

The methods have a call to Thread.Sleep to simulate a long running web method (otherwise the method will return immediately without giving the feel of needing any async behaviour) :)

Now for the jQuery part. For this, we will call these webmethods through our AJAX wrapper (a bit modified) and attach handlers to the various events offered by promise. Now since the events themselves return a promise, these can be chained to each other like other jQuery constructs.

JavaScript
function AjaxWrapper(webMethodName, parameterListInJsonFormat) {
    var promiseObject = $.ajax({
        url: "Test.aspx/" + webMethodName,
        contentType: "application/json; charset=utf-8",
        type: "POST",
        input: parameterListInJsonFormat
    });

    return promiseObject;
}

// Pass empty parameter cause our method doesn't expects anything
var helloPromise = AjaxWrapper("ReturnHello", {});
var worldPromise = AjaxWrapper("ReturnWorld", {});

// Bind the "done" event with the handlers
helloPromise.done(function (data) {
    alert("Individual task 1 :" + data.d);
});
worldPromise.done(function (data) {
    alert("Individual task 2:" + data.d);
});

// Another handler to the 'done' event, illustrating multi-binding
helloPromise.done(function () {
    alert("Too many entities want to say 'Hello'");
});

// This call will throw an error on service side, 
//   and the exception detail will be provided to the promise to handle that
var errorThrowPromise = AjaxWrapper("ReturnException", {});

errorThrowPromise.error(function (errorObj) {
    alert("This error was thrown " + errorObj.responseText);
});
errorThrowPromise.always(function () {
    alert("Although the webservice call gave an error,
                      this will always run, just like the 'finally' clause");
});

Following is the result of running this script

Comes after 5 seconds (thread wait in method 1)

Comes immediate after above, in fact at same time but due to explicit click required for 'Ok' button

Comes after 10 sec (wait here for 2nd method call)

Comes as a result of exception thrown in the 3rd web method. Note the entire exception details are given, including the stack trace, since I just outputed the responseText. This needs to be filtered to provide a proper handling on client side

This is a result of "always" call, illustrating something to happen regardless of service call's status

Also, we can combine multiple promises together, to perform a work which requires information from multiple web service calls. The script below will combine the Hello and World service calls to show a joined "Hello World" :)

C#
// Now illustatrate combining of promises
//  Multiple promises can be fulfilled at once :)
var combinedPromise = $.when(helloPromise, worldPromise);

// Combine the result of two promises to perform a joint task
combinedPromise.done(function (firstResult, secondResult) {
    alert("Combined result :" + firstResult[0].d + " " + secondResult[0].d);
});

This shows the following output

That's a small introduction to this powerful feature. If you really want to explore more, have a look at some of the articles below which I read to get good ideas.

Read more here:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)