Praveen Singh

dojo xhr using DEFERRED AND PROMISE concepts

Introduction


DOJO TOOLKIT TUTORIAL (AMD): Ajax/XHR call Dojo with concept of Deferred and Promise.

dojo/Deferred is a class that is used as the foundation for managing asynchronous threads in Dojo. Packages like dojo/request use this class to return a promise that gets resolved when the asynchronous thread is complete. In order trigger a callback to occur when the thread is complete, the .then() method is used. As well as the thread can be informed to cancel itself by using the .cancel() method. If the thread has completed, then the .then() callback will be executed immediately.

Must pre-reads


If you are not comfortable with Promise and Deferred concepts or How Asynchronous Javascript work, with help of event queue. Yo should stop here and read the below article first.

If you are not comfortable with basic DOJO api of Promise and Deferred, you should checkout below article first.

If you are not comfortable with Dojo in general, you should hit the tab which says Dojo at top and pick the article with your level.


Step by step video explanation


code hands-on


Package Structure

Lets build the package structure as in below image

If you are facing problem in running the application with this package structure, you might want to check the "Dojo hello world" application in DOJO section of this blog.


We will concentrate on building deferred-xhr and server folders in this article.

Always run DOJO in server

As per Dojo guideline, dojo work best when it is server. So we need a static server to hold everything

If you are on mac, come to application directory and simply run python -m SimpleHTTPServer 8888 to run application on port 8888

You should see the whole package structure by hitting http://localhost:8888/

You can choose your favorite server. Things will not change much.


code

Build the server

We will be using a simple nodejs server for all server response in this application

There is no dependency on what technology you use on Server, you can use Java, PHP, etc...till they follow the URL pattern

Path: /app/dojo-deferred/server/server.js
var http = require("http"),
    SUCCESS_CODE = 200,
    FILE_NOT_FOUND_CODE = 404,
    PORT = 9999,
    HEADERS = {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"
    };
var server = http.createServer(handleRequest);
server.listen(PORT);
console.log("Listening at http://localhost:" + PORT);

function handleRequest(req, res) {

    var data = {};

    console.log("req.url: ", req.url);
    if (req.url === "/login.json") {
        data = handleAuthentication();
    } else if (req.url === "/topproducts.json") {
        data = returnTopProducts();
    } else if (req.url === "/dealOfTheDay.json") {
        data = dealOfTheDay();
    } else if (req.url === "/feedback.json") {
        data = feedback();
    } else {
        res.writeHead(FILE_NOT_FOUND_CODE, HEADERS);
        res.end(JSON.stringify({
            error: null
        }) + "\n");
    }

    console.log("Putting delay of 1 sec");
    setTimeout(function() {
        res.writeHead(SUCCESS_CODE, HEADERS);
        res.end(JSON.stringify(data) + "\n");
    }, 1000);
}

function handleAuthentication() {
    console.log("In handleAuthentication");
    var user = {
        custId: 12345,
        name: "Praveen from icodingclub"
    };
    return user;
}

function returnTopProducts() {
    console.log("In returnTopProducts");
    var products = [{
        id: 111,
        name: "iphone 5"
    }, {
        id: 222,
        name: "Moto X"
    }];

    return products;
}

function dealOfTheDay() {
    console.log("In dealOfTheDay");
    var products = [{
        id: 333,
        name: "iPad Air"
    }];

    return products;
}

function feedback() {
    console.log("In feedback");
    var products = [{
        id: 444,
        name: "Macbook Air"
    }];

    return products;
}
(To run on terminal) $ node server.js

At this point, you should see the below response on hitting the URL shown in image

Building main test HTML

Path: /app/dojo-deferred/deferred-xhr/testXhr.html

Build the main widget

Path:/app/dojo-deferred/deferred-xhr/TestDefXhrWidget.js
define([
    "dojo/_base/declare",
    "dojo/Deferred",
    "dojo/request"
], function(declare, Deferred, request) {

    return declare(null, {

        constructor: function() {
            // I have used self and not .bind(this) for clarity.
            var self = this;

            request("http://localhost:9999/login.json").then(function(user) {
                self.showGreatings(user);
            })
        },

        showGreatings: function(user) {
            console.log("Greetings: ", user);
        }
    });
});

        
Log output:
  Greetings:  {"custId":12345,"name":"Praveen from icodingclub"}  
  

Try chaining

Let's re-look on the problem statement, which we discussed in 1st blog

Callback solution

  1. You need to develop a home page of e-shopping site.
  2. first you have to take login credential from user, make a server call for authentication.
  3. On successful authentication, show greeting message for user.
  4. Show loading icon in main section.
  5. Make a server call to show home page with “Top Products”
  6. Once it is done, take a server call to get “Deals of the day”, and show on the right side of home page.
  7. Once it is done, get the information of last product user had purchased from site and show the pop up, “do you want to review this product?”

Solution using callback.

            
xhr('/api/login', function(user) {
    showGrretings();
    showLoadingIcon();
    xhr('/topproducts', function(products) {
        showProducts();
        xhr('/dealOfTheDay', function(products) {
            showDealOfTheDay();
            xhr('/reviewLastPurchaseProduct', function(products) {
                showRevieDialog();
            });
        });
    });
});

        

Problem using callback.

In above case, more the level of nesting, the harder the code is to read, debug, maintain, upgrade, and basically work with. This is generally known as "callback hell" or "Pyramid of Doom".

Also, if we needed to handle errors, we need to possibly pass in another function to each xhr call to tell it what it needs to do in case of an error.

If we wanted to have just one common error handler, that is not possible.

Solution using Promise.

define([
    "dojo/_base/declare",
    "dojo/Deferred",
    "dojo/request"
], function(declare, Deferred, request) {

    return declare(null, {

        constructor: function() {
            // I have used self and not .bind(this) for clarity.
            var self = this;

            request("http://localhost:9999/login.json").then(function(user) {
                self.showGreatings(user);
            }).then(function() {
                return request("http://localhost:9999/topproducts.json");
            }).then(function(products) {
                self.showProducts(products);
            }).then(function(data) {
                return request("http://localhost:9999/dealOfTheDay.json");
            }).then(function(dealOfTheDay) {
                self.showDealOfTheDay(dealOfTheDay);
            }).then(function(data) {
                return request("http://localhost:9999/feedback.json");
            }).then(function(purchasedProduct) {
                self.showFeedback(purchasedProduct);
            });
        },

        showGreatings: function(user) {
            console.log("Greetings: ", user);
        },
        showProducts: function(products) {
            console.log("showing products: ", products);
        },

        showDealOfTheDay: function(dealOfTheDay) {
            console.log("showing deal of the days: ", dealOfTheDay);
        },

        showFeedback: function(purchasedProduct) {
            console.log("Ask feedback for: ", purchasedProduct);
        }
    });
});

Log output:
Greetings:  {"custId":12345,"name":"Praveen from icodingclub"}
showing products:  [{"id":111,"name":"iphone 5"},{"id":222,"name":"Moto X"}]
showing deal of the days:  [{"id":333,"name":"iPad Air"}]
Ask feedback for:  [{"id":444,"name":"Macbook Air"}]

Try error post 404 (without short-circuit)

changing url to login.json.404 from login.json, to trigger 404


constructor: function() {
    // I have used self and not .bind(this) for clarity.
    var self = this;

    //changing path for 404 error code
    request("http://localhost:9999/login.json.404").then(function(user) {
        self.showGreatings(user);
    }, function(err) {
        console.log("Handling error gracefully!!");
    }).then(function() {
        return request("http://localhost:9999/topproducts.json");
    }).then(function(products) {
        self.showProducts(products);
    }).then(function(data) {
        return request("http://localhost:9999/dealOfTheDay.json");
    }).then(function(dealOfTheDay) {
        self.showDealOfTheDay(dealOfTheDay);
    }).then(function(data) {
        return request("http://localhost:9999/feedback.json");
    }).then(function(purchasedProduct) {
        self.showFeedback(purchasedProduct);
    });
}

Log output:
Handling error gracefully!!
showing products:  [{"id":111,"name":"iphone 5"},{"id":222,"name":"Moto X"}]
showing deal of the days:  [{"id":333,"name":"iPad Air"}]
Ask feedback for:  [{"id":444,"name":"Macbook Air"}]

Try error post 404 (short-circuit)


constructor: function() {
    // I have used self and not .bind(this) for clarity.
    var self = this;

    //changing path for 404 error code
    request("http://localhost:9999/login.json.404").then(function(user) {
        self.showGreatings(user);
    }).then(function() {
        return request("http://localhost:9999/topproducts.json");
    }).then(function(products) {
        self.showProducts(products);
    }).then(function(data) {
        return request("http://localhost:9999/dealOfTheDay.json");
    }).then(function(dealOfTheDay) {
        self.showDealOfTheDay(dealOfTheDay);
    }).then(function(data) {
        return request("http://localhost:9999/feedback.json");
    }).then(function(purchasedProduct) {
        self.showFeedback(purchasedProduct);
    },function(err) {
        console.log("Handling error gracefully!!");
    });
}

Log output:
Handling error gracefully!!

dojo when

dojo/when is designed to make it easier to work with synchronous and asynchronous process together. Accepts promises but also transparently handles non-promises

What that means is, if you are calling a function, which you don't know will return a Promise or array, you can use when there.

Extremely handy when you want to cache data return from server and want to server call only when data is null

define([
    "dojo/_base/declare",
    "dojo/when",
    "dojo/request"
], function(declare, when, request) {

    return declare(null, {

        greetingPromise: null,

        constructor: function() {
            // I have used self and not .bind(this) for clarity.
            var self = this;

            when(self.greatingCache(), function(user) {
                self.showGreatings(user);
            });

            setTimeout(function() {

                when(self.greatingCache(), function(user) {
                    self.showGreatings(user);
                });

            }, 5000);
        },

        greatingCache: function() {

            if (this.greetingPromise === null) {
                console.log("CACHE MISS");
                this.greetingPromise = request("http://localhost:9999/login.json");
            } else {
                console.log("CACHE HIT");
            }
            return this.greetingPromise
        },

        showGreatings: function(user) {
            console.log("Greetings: ", user);
        }
    });
});

        

Contact Me


If you want to contact me for feedback or suggestion or doubt. You can do so by commenting on Blogger article or in Youtube comments section.

You can also approach me dropping me mail directly at mails.icodingclub@gmail.com ( Prefer: comments approach)

1 comment:

  1. I was extremely pleased to uncover this page. I need to to thank you for your time for this fantastic read!! I definitely really liked every little bit of it and i also have you bookmarked to check out new things in your blog.
    Price Comparison

    ReplyDelete