Skip to Content

Hello All,

I tried to send OData requests from SAP UI5 application, which make use of SAP OData Model (sap.ui.model.odata.v2.ODataModel), I found that all the calls which were made to back end were $batch requests, Which encapsulates the multiple operations together and sent at the end of method execution.

Reference for model:

https://sapui5.netweaver.ondemand.com/sdk/#docs/api/symbols/sap.ui.model.odata.v2.ODataModel.html

On Research, I found there are multiple ways to send the OData Back-to-Back calls in a synchronous way. This is required especially when you want to make a call, Collect the result of that and make use of that result in next call. Following are some of the techniques:

Easiest of them all is make use of an OData request parameter “async” : false while making call, But with OData Model V2 this is not helpful as it is combining multiple requests in a $batch.

Make a call and in callback function (which is called once request is completed), call the next request. here is an example

var payload = { Your JSON payload here };

myODataModel.create( “/EntitySet”, payload, {

             success: function(response){

              // this is the function which gets invoked asynchronously when the request is successful

                    myODataModel.read(“/Entityset”,{

                        success: function(response){

                           //here you can read the response

                            }});

           }});

In addition, I also found that the “this” pointer object which helps me accessing global controller variable is not more valid inside the callback function. Hence I have to create a local variable (before the first call) which has a valid scope for internal calls. I named it as variable that, below is the code snippet:

   var that = this;

   myODataModel.create( “/EntitySet”, payload, {

             success: function(response){

              // this is the function which gets invoked asynchronously when the request is successful

                    myODataModel.read(“/Entityset”,{

                        success: function(response){

                           //here you can read the response and global variables

                          // that.somecontrollervariables

                            }});

           }});

Please note that Java script is case sensitive hence you need to be careful in method names, entity set names and call back parameter names, I recommend to look at documentation as reference for model link.

Now comes the main challenge, where I have to make more than 10 call but they all needs to be synchronized (output of one call to input of next one), I have all my calls inside a collection and I will fire them one after another (Dynamic table which has requests information). In such case coding a method inside method is practically not possible (as I am not that great programmer in JS). Hence I use function recursion to achieve the same.

Below is a small flow chart to explain

Here is the code

buffer: [],

                        //Changes Begins

performOperation: function(singleOpr,oCallerModel){

                                    var that = this;

                                    //kill the recursion

                                    if(that.oprIndex == that.totIndex){

                                                return “”;

                                    }

                                   

                                    //On success call recursion

                                    var fnNextRequest = function(resposnse){

                                                this.buffer.push({

                                                            “seqNo”: singleOpr.operationSeq,

                                                            “response”: resposnse

                                                });

                                                this.performOperation(this.operations.oDataOprCollection[this.oprIndex],oCallerModel).bind(this);

                                    };

                                   

                                    //On Failure Show error using utility

                                    var fnErrorDetail = function(oError) {

                                                jQuery.sap.log.info(oError);

                                                Utility._showErrorMsg(oError);

                                    };

                                   

                                    //Check operation type and call respective method

                                    switch (singleOpr.method) {

                                    case “GET”:

                                                var inpParams = this.getSearchParms(singleOpr.search);

                                                //Call GET with parameters, Note that we use jQuery proxy to be able to access global controller

                                                //object as “this”

                                                oCallerModel.read(singleOpr.entitySet,{

                                                            urlParameters: inpParams,

                                                            //async: false,

                                                            success: jQuery.proxy(fnNextRequest, this),

                                                            error: jQuery.proxy(fnErrorDetail, this)

                                                }).bind(this);                                                              

                                                break;

                                    case “POST”:

                                                //Call Post and prepare payload using reusable function

                                                oCallerModel.create(singleOpr.entitySet,this.preparePayload(singleOpr.payload, singleOpr.replacements),{

                                                            //async: false,

                                                            success: jQuery.proxy(fnNextRequest, this),

                                                            error: jQuery.proxy(fnErrorDetail, this)

                                                });                   

                                                break;

                                    default:

                                               

                                                break;

                                    }

                        },

                        operations: [],

                        oprIndex: 0,

                        totIndex: 0,

                        handleODataRequests: function(oData){

                                   

                                                //parse XML and Fill the data

                                                var xmlDOM = “”;

                                                //Prepares the collection of OData Operations

                                                this.operations = this.prepareOperationCollection(xmlDOM);

                                                //Sets the index as total no of calls to be made

                                                this.totIndex = this.operations.oDataOprCollection.length;

                                                //Sets the service name and namespace

                                                var serviceName = this.operations.serviceNS + this.operations.serviceName;

                                                //Creates new OData Model Object (using relative path)

                        var oCallerModel = new sap.ui.model.odata.v2.ODataModel(serviceName);

                                                //if there is atleast one operation – Shoot

                                                if(this.totIndex > 0){

                                                            this.performOperation(this.operations.oDataOprCollection[this.oprIndex],oCallerModel).bind(this);

                                                }

                        },

 

Excuse please for hiding some re-use methods. You can adapt accordingly.

Use case: Suppose you would like to test end to end business processes without using the user interface of application, This end-to-end test would need you to process OData Calls in sequence and synchronously. For example first we create a sales order, then a delivery and finally billing document. To perform all these calls, the data will be transferred from one step to another hence, you need to first make a POST on sales order, Then GET the sales order ID and pass it to next POST call for creating delivery Document. This approach can be used to create a Fiori UI and do the test in automated way, User just need to pass the required data and you can hard code the calls with users data.

BOOM!! A new way of End-to-End Process test. J

 

To report this post you need to login first.

6 Comments

You must be Logged on to comment or reply to a post.

  1. A B Post author

    Hello Michal,

    Can you please help with Pseudo-code for calling multiple requests one after another and passing output of one to next one using promise.

    I have all operations to be performed in a table which is a variable in controller.

    Thanks,

    Anurag

    (0) 
    1. Michał Majer

      Check below example, You can call promise method, and call next method after request ends.

      First You create delivery -> then invoice -> then … (Yes, this is callback hell).

      /* Methods which call requests */
      createDeliveryInERP: function(oDeliveryHeader, tDeliveryPositions) {
       var oDeferred = jQuery.Deferred();
       oErp.callFunction("/Action", {
        method: "POST",
        success: function(oDataResult) {
           oDeferred.resolve(oDataResult); //Return data from request
        },
        error: function(oError) {
           oDeferred.resolve(oError);
        }
       });
       return oDeferred;
      }
      
      createInvoiceInERP: function(oInvoiceHeader, tInvoicePositions) {
       var oDeferred = jQuery.Deferred();
       oErp.callFunction("/Action", {
        method: "POST",
        success: function(oDataResult) {
           oDeferred.resolve(oDataResult); //Return data from request
        },
        error: function(oError) {
           oDeferred.resolve(oError);
        }
       });
       return oDeferred;
      }
      
      /* Example: How to call? */
      var oController = this;
      this.createDeliveryInERP().then(function(oDataResult) {
       //oDataResults contains data from createDeliveryInERP request
       oController.prepareHeaderForInvoiceExample();
       oController.createInvoiceInERP().then(function(oDataResults) {
        // Invoice created after delivery simple example
       });
      });
      

       

      (0) 
      1. Nick Yang

        Hi Michal,

         

        In your last part of the example code. I will write it in this way to avoid making extra variable (oController) declaration.

        this.createDeliveryInERP().then(function(oDataResult) {
         //oDataResults contains data from createDeliveryInERP request
         this.prepareHeaderForInvoiceExample();
         this.createInvoiceInERP().then(function(oDataResults) {
          // Invoice created after delivery simple example
         });
        }.bind(this));

        .bind() is part of ECMAScript 5 standard.

         

        Regards,

        Nick

        (0) 

Leave a Reply