Skip to Content
Author's profile photo Jose Muñoz Herrera

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:

  1. Request data to a OData service 1
  2. 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:

  1. Employees for service ( http://services.odata.org/V4/Northwind/Northwind.svc/ ) are read
  2. 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) );


jsbin example

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.

  1. “oModel2: Metadata loaded OK”
  2. “oModel: Metadata loaded OK”
  3. “oModel: Employees read successfully!”
  4. “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.

JS Bin – example 2

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!

Assigned Tags

      9 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Maksim Rashchynski
      Maksim Rashchynski

      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

      1.    oModel2.attachEventOnce("metadataLoaded", function() { 
      2.                 console.log("oModel2: Metadata loaded OK"); 
      3.                 this.oModel2DataDeferred.resolve(); 
      4.             }, this); 


      redundant

      Author's profile photo Jose Muñoz Herrera
      Jose Muñoz Herrera
      Blog Post Author

      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

      Author's profile photo Volker Buzek
      Volker Buzek

      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.

      Author's profile photo Satish Ramteare
      Satish Ramteare

      Promise looks very promising:)

      I wanted to know if UI5 supports promise?

       

      Author's profile photo Volker Buzek
      Volker Buzek

      yes, also cross-browser, incl IE - UI5 comes with Promise-polyfills.

      Author's profile photo Sakthi kumar
      Sakthi kumar

      @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??

      Author's profile photo Volker Buzek
      Volker Buzek

      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:

      function fn1() {
          return new Promise((fnResolve, fnReject) => {
              odata.read({
                  // ...
                  success: fnResolve(oResponse),
                  error: fnReject()
              })
          })
      }
      
      function fn2() {
          return new Promise((fnResolve, fnReject) => {
              odata.create({
                  // ...
                  success: fnResolve(oResponse),
                  error: fnReject()
              })
          })
      }
      
      // with Promise chain
      function fn3() {
          return fn1()
              .then(fn2)
              .catch(oError => {
                  throw oError
              })    
      }
      //fn3().then(...).catch(oError=>console.error(oError))
      
      // with async/await
      async function fn4() {
          try {
              await fn1()
              await fn2()
          } catch (oError) {
              throw oError
          }
      
      }
      // let myResult; try { myResult = await fn4() } catch (oError) { throw oError }
      
      Author's profile photo Sakthi kumar
      Sakthi kumar

      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");

      }

       

       

       

      Author's profile photo Jose Munoz Herrera
      Jose Munoz Herrera

      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