06.Asynchronous processing
- Overview
- About jQuery Deferred API
- Wait for asynchronous processing
- Asynchronous processing chain
- Frequently asked question
- Reference
Overview
This chapter describes how to handle asynchronous processing, which is essential for implementing rich, high operability but easy-to-use views.
Using asynchronous processing for exchanging data with server, you can reduce view transition, and browser will not be blocked during communicating with server. Asynchronous processing is essential for creating a rich but easy-to-use web application.
This chapter describes about asynchronous processing via an example that asynchronously retrieves and displays product list.
Implementation example
Write the code that retrieves data from server to logic, and code that performs view manipulation to controller.
The following code describes the logic that retrieves product information that matches category selected on view from server asynchronously.
/**
* Logic name
*/
__name: 'ItemSearchLogic',
/**
* Get product list (product name & tax-included price).
*
* @param categoryId {Number} category ID
* @returns product list
*/
getItemList: function(categoryId) {
var dfd = this.deferred();
var result = null;
this._getItemData(categoryId).done(function(data) {
result = $.map(data, function(obj) {
obj.price = Math.floor(obj.price * 1.05);
return obj;
});
dfd.resolve(result);
}).fail(function(error) {
dfd.reject(error.message);
});
return dfd.promise();
},
/**
* Get product list (product name and tax-excluded price) based on categoryID from server
*
* @param categoryId {Number} category ID
* @returns product list
*/
_getItemData: function(categoryId) {
return h5.ajax({ //Returns JSON object: [ {"itemname":"hoge", "price": "1000"}, ...]
type: 'GET',
dataType: 'json',
url: './itemList',
data: 'categoryId=' + categoryId
});
}
};
- getItemList is a public method, and is assumed to be called from inside of the controller.
By dividing the roles between method that retrieves data and method that processes retrieved data, if you want to execute processes other than getItemList, keep the method that retrieves data (_getItemData) as-is and simply create a new processing method. - A Deffered object is created in getItemList. As described in Logic chapter, the framework will automatically add deferred method in logic.
- Return Promise object in getItemList.
- $.ajax in _getItemData is a built-in method in jQuery that performs Ajax communication. Since ajax method returns a Promise object, you can retrieve asynchronous processing results by using done() method. In this example, you can use this._getItemData(categoryId).done() to retrieve data. Besides, you can retrieve error by using fail method.
The controller is written as below:
/**
* Controller name
*/
__name: 'ItemSearchController',
/**
* Template
*/
__templates: 'template.ejs',
/**
* Product searching logic
*/
itemSearchLogic: itemSearchLogic,
/**
* Search button pressed event
*/
'#searchBtn click': function(context) {
var $resultDiv = this.$find('#resultList');
var that = this;
$resultDiv.empty();
// Category selected on view
var category = this.$find('#select-category option:selected').val();
this.itemSearchLogic.getItemList(category).done(function(resultData) {
that.view.append($resultDiv, 'template1', {listData: resultData});
}).fail(function(errMsg) {
alert('Failed to retrieve data' + errMsg);
});
}
};
h5.core.controller('#container', itemSearchController);
- Execute itemSearchLogic#getItemList. A Promise object should be returned.
- Define controller action after logic asynchronous processing completed to done callback.
This example will print the retrieved data to view as a list using template.
Checking operation
About jQuery Deferred API
Deffered method used in hifive asynchronous control is based on jQuery Deferred API.
Therefore, basically, all jQuery Deferred API functions can be utilized.
Implementation Example of deferred.notify, deferred.progress
Add code that uses notify, progress to the above implementation example.
/**
* Logic name
*/
__name: 'ItemSearchLogic',
/**
* Get product list (product name & tax-included price).
*
* @param categoryId {Number} Category ID
* @returns product list
*/
getItemList: function(categoryId) {
var dfd = this.deferred();
var result = null;
this._getItemData(categoryId).done(function(data) {
result = $.map(data, function(obj) {
dfd.notify(data.length);
obj.price = Math.floor(obj.price * 1.05);
return obj;
});
dfd.resolve(result);
}).fail(function(error) {
dfd.reject(error.message);
});
return dfd.promise();
},
/**
* Get product list (product name and tax-excluded price) based on categoryID from server.
*
* @param categoryId {Number} Category ID
* @returns product list
*/
_getItemData: function(categoryId) {
return $.ajax({ // Return JSON object: [ {"itemname":"hoge", "price": "1000"}, ...]
type: 'GET',
dataType: 'json',
url: './itemList',
data: 'categoryId=' + categoryId
});
}
};
- deferred.notify is used in _getItemData() done callback. Number of items where tax-included price calculation for each data completed (progress) is displayed to view.
This example describes a simple process and it doesn't take much time to complete. However, for time-consuming process, it would be helpful since results can be displayed while the process is still being executed.
/**
* Controller name
*/
__name: 'ItemSearchController',
/**
* Template
*/
__templates: 'template.ejs',
/**
* Product searching logic
*/
itemSearchLogic: itemSearchLogic,
/**
* Search button pressed event
*/
'#searchBtn click': function(context) {
var indicator = this.indicator({
message: 'Searching',
target: document
}).show();
// Category selected on view
var category = $('#select-category option:selected').val();
var $resultDiv = this.$find('#resultList');
var that = this;
var count = 0;
$resultDiv.empty();
this.itemSearchLogic.getItemList(category).done(function(resultData) {
that.view.append($resultDiv, 'template1', {listData: resultData});
indicator.hide();
}).fail(function(errMsg) {
alert('Failed to retrieve data' + errMsg);
indicator.hide();
}).progress(function(total) {
indicator.message('Searching' + ++count + '/' + total);
});
}
};
h5.core.controller('#container', itemSearchController);
- Call progress method from return value of getItemList (promise object) to register progress callback function.
- progress callback executes the process that prints to view number of completed processes.
Checking Operation
Wait for asynchronous processing
Using h5.async.when(), a user can wait until multiple promise objects that are passed to the argument are resolve(). This is similar to $.when(). Multiple promise objects passed to the argument can be retrieved using variable length list of arguments. Besides, unlike $.when(), you can get promise object array from the argument.
API Documenth5.async.when
Implementation example
Defining in a logic class
// _getDepart(), _getReturn(), _getHotel() execute asynchronous processings respectively, and return promise objects.
// You can define action to perform after 3 processes completed in getPlanList() done callback.
getPlanList: function() {
// Call 3 methods and wait
// When all 3 asynchronous processings completed, done callback is called.
return h5.async.when(this._getDepart(), this._getReturn(), this._getHotel());
},
Defining in a controller class (call getPlanList method)
function(depatFlightList, returnFlightList, hotelList) {
that.log.debug("Completed retrieving data");
});
Checking operation
TBD
Asynchronous processing chain
Use then() if you want to execute next asynchronous processing after a process completed.
(Use pipe() for jQuery 1.8 or older ))
Implementation example
// Wait for promise object result to be returned in then
// after that, execute process registered in next method chain.
return h5.ajax(result.url);
}).then(function(){
//Define the process that executes when h5.ajax(result.url) completed successfully
});
Checking operation
TBD
Frequently asked question
How should logic API granularity be decided?
Each asynchronous processing should be treated as a single method.
Using deferred, you can manipulate flexibly even if the order of asynchronous processings change, or when other asynchronous processings are added in the chain.
What should I do if I want to display to view while the process is being executed?
You can display the progress to view before completion notice using deferred notify() method.
Since callback function registered in promise progress() is also called if deferred notify() method is called,
you can make use of this to redraw view.
notify() method can retrieve object in the argument.
Since passed object will be forwarded to the argument of the callback function that is set by progress(), data can be updated to view.
What should I do if data is huge, or it takes much time until the process completed?
Since callback function can be called while the process is still in progress using progress() method, in case there are many processes to handle, configure logic API to “pass arrays and objects in batches and use progress() where necessary”.
Next Chapter ⇒07. Unit Testing