Deferred OData requests with SAPUI5 and jQuery
Introduction
This blog post describes how to call multiple OData requests in parallel and wait all them to finish so that when we have all the responses and after that do something else.
Use case
Imagine that you have two services, first for data and second for authorizations, and you have to activate a button only if first service gives you certain data and second service gives you correct auths. You have to do following two steps:
- Request data to a OData service 1
- Request data to OData service 2 to check authorizations
But only If 1 and 2 are done and data is retrieved then you have to activate a button.
For that you can use jQuery deferred objects and Promises.
Check following liks for help if you are not familiar with that concepts.
https://api.jquery.com/jquery.deferred/
https://api.jquery.com/jquery.when/
Some examples
https://learn.jquery.com/code-organization/deferreds/examples/
Let’s start with SAPUI5
So we will create one Deferred object for each condition we want to wait and resolve the object when required.
In following example with jQuery.Deferred() we create the object. After that we can create an ODataModel and attach to the metadataLoaded event. Finally when metadata is loaded the Deferred object will be resolved.
this.oModel2DataDeferred = jQuery.Deferred();
var sURI2 = "https://cors-anywhere.herokuapp.com/services.odata.org/V3/OData/OData.svc/";
var oModel2 = new sap.ui.model.odata.v2.ODataModel(sURI2,oConfig);
oModel2.attachEventOnce("metadataLoaded", function() {
console.log("oModel2: Metadata loaded OK");
this.oModel2DataDeferred.resolve();
}, this);
var finishWait = function(){
console.log("this is the end");
}
We can increase the complexity. We will call another service and another Deferred object but now Deferred object will be resolved when we have a response for a requested data. So if we read the Employees and request was ok, success function will be read and Deferred object is resolved.
this.oModelDataDeferred = jQuery.Deferred();
var sURI = "https://cors-anywhere.herokuapp.com/services.odata.org/Northwind/Northwind.svc/";
var oModel = new sap.ui.model.odata.v2.ODataModel(sURI,oConfig);
oModel.attachEventOnce("metadataLoaded", function() {
console.log("oModel: Metadata loaded OK");
}, this);
oModel.read("/Employees", {
success: jQuery.proxy(fSuccess, this),
error: jQuery.proxy(fError, this)
});
function fSuccess(oEvent){
console.log("oModel: Employees read successfully!");
this.oModelDataDeferred.resolve();
};
function fError(oEvent){
console.log("oModel: An error occured while reading Employees!");
};
How do we wait both Deferred objects to be resolved? Using jQuery.when that returns a Promise that will be resolved as soon as all Deferred objects passed as parameters are resolved.
In our example function finishWait will only be called if both Deferred ( oModelDataDeferred & oModel2DataDeferred ) are resolved, that also means that finishWait is called if:
- Employees for service ( http://services.odata.org/V4/Northwind/Northwind.svc/ ) are read
- Metadata for service ( http://services.odata.org/V3/OData/OData.svc/ ) is ready
var finishWait = function(){
console.log("this is the end");
}
jQuery.when(this.oModelDataDeferred,
this.oModel2DataDeferred )
.done( jQuery.proxy(finishWait,this) );
If you check the example you will see the following messages in the console but the order of appearence of messages 1 2 & 3 is never known. Message 4 will be always the last message for sure.
- “oModel2: Metadata loaded OK”
- “oModel: Metadata loaded OK”
- “oModel: Employees read successfully!”
- “this is the end”
Edited on 15/06/2016:
After reading the comments of Buzek Volker and Maksim Rashchynski I decided to create another example implementing the Promise.all and using the metadataLoaded() method.
I hope that update helps
Conclusion
With that we have parallel batch requests with SAPUI5 and we have a way to wait all requests using jQuery.
I hope this blog helps someone with same requirements and if you have any doubts or recomendations do not hesitate to contact me
Thanks a lot!
from 1.30 there is metadataLoaded method which returns promise
https://sapui5.netweaver.ondemand.com/sdk/#docs/api/symbols/sap.ui.model.odata.v2.ODataModel.html#metadataLoaded
which makes
redundant
Hi Maksim,
Thanks for the info, I'll take into account for the next upgrade as I'm still working with 1.28 as it is the current long term support SAPUI5 libraries.
Thanks a lot
Hi Jose,
if you use Promise.all() instead of chaining jQuery.Deferred's, then you get the best of all worlds:
- Promises executing in parallel
- Promise.all() only resolves after its last Promise has resolved
- Promise.all() gives you access to the results/resolves of all contained Promises
Pseudo-Code:
Promise.all( [ oModel.loadData(), oModel1.loadData() ] )
.then( console.log(resulsts) );
- V.
Promise looks very promising:)
I wanted to know if UI5 supports promise?
yes, also cross-browser, incl IE - UI5 comes with Promise-polyfills.
@Jose Muñoz Herrera @
Hi have two odata calls in two methods.
function1(){ odata.read(succes:{resolve(odataResponse)})}
function2(){ odata.Create(do something}
from a different function, say function3, the call is like this:
function3()
{
this.oModel2DataDeferred = jQuery.Deferred();
this.function1();
jQuery.when(this.oModel2DataDeferred ).done( this.function2(),this );
}
The problem is function1 is executed till odata read call. Then it jumps to function2, executes the create call and then executes the read call in function1.Same is the scenario with Promises.
I want the read call to be executed first and then create call should happen. Can you please help??
you are experiencing async side-effects.
the art lies in mastering the sequence of the async chain ? - and not to use jQuery.Deferred any more (UI5 comes with Promise polyfills!!!)
With ES6+, you could do:
This is where i am facing issue. I keep resolve inside the odata success call back of fn1(). During fn1 call execution, when the odata read is found, the control goes to function fn2() and create call is executed first. After that the read call is executed, which is not supposed to happen. Are the below syntax fine?
callPromise: function () {
var t = this;
var pr1 = this.loadTableandHeader("readBo");
pr1.then(function(readResponse){
t.loadBudget(readResponse);
}.bind(t));
},
loadTableandHeader: function (groupId) {
var t =this;
return new Promise(function (resolve, reject)
{
odata.read("path",{success:function(){
resolve(d);
}})
});
}),
loadBudget: function (readResponse) {
odata.create("pathcreate");
}
Hello,
You are already calling function2 in the .done( ):
jQuery.when(this.oModel2DataDeferred ).done( this.function2(),this );
You should not add the () and try something like:
jQuery.when(this.oModel2DataDeferred ).done( this.function2,this );
Regards