Multiple parallel calls using Javascript policy

sujnana
Participant IV

Is it possible to make parallel asynchronous call using Javascript httpclient with callback (without using waitForComplete())? The requirement is to call multiple requests (up to 5) & mashup the result. We developed a for proxy for this in 2016 using Javascript httpclient which uses waitForComplete(). In preflow request we make multiple requests like below.

searchRequests.forEach(function(searchTypeValue) {
	var searchTarget = callbackUrl +"/documents/search"+"?"+searchTypeBackeEndProp+"="+searchTypeValue;
	var eventReq = new Request(searchTarget, "GET", headers);
	var exchange  = httpClient.send(eventReq);
	searchObject.push({"calloutObject": exchange,"searchTypeValue":searchTypeValue, "searchTypeRequestProp":searchTypeRequestProp, "searchTypeBackeEndProp":searchTypeBackeEndProp, "fieldName":fieldName});
			});
context.setVariable("searchObject", searchObject);

And in post response flow, we assemble responses something like below

var searchObject =  context.getVariable("searchObject");

var searchRecords=[];
for(var key in searchObject){
	if(searchObject.hasOwnProperty(key)) {
		var search = searchObject[key].calloutObject;
		var searchTypeValue = searchObject[key].searchTypeValue;
		var searchTypeRequestProp = searchObject[key].searchTypeRequestProp;
		var searchTypeBackeEndProp = searchObject[key].searchTypeBackeEndProp;
		var fieldName = searchObject[key].fieldName;
		searchRecords[key]=getsearchRecords(search, searchTypeValue, classValue, searchTypeRequestProp, searchTypeBackeEndProp,fieldName);
	}
}
var searchResponse = {};
status = "true";
searchResponse.status = status;
searchResponse.searchRecords = searchRecords; 
response.headers['Content-Type']='application/json'; 
context.setVariable("response.content",JSON.stringify(searchResponse));





 this.getsearchRecords = function(exchange, searchTypeValue, classValue, searchTypeRequestProp, searchTypeBackeEndProp, fieldName) {
		var searchRecordsValue={};
		var respContent;

        	exchange.waitForComplete();

        	if (exchange.isSuccess()) {
//Construct response and the result
		}
}


As per this link waitForComplete is anti pattern - https://docs.apigee.com/api-platform/antipatterns/wait-for-complete

Is it possible rewrite this in java script without using waitForComplete? I think async/await or Promise doesn't work in Apigee JS policy.

Note: I am not looking NodeJs (hosted target) solution due to Apigee limits (max 20) per org.

Solved Solved
0 3 2,603
1 ACCEPTED SOLUTION

This JS will send out parallel JS calls, but it will wait. It does not defer the waiting to a separate policy.

function generateCallback(state) {
  return function(response, error) {
    ....
  };
}


searchRequests.forEach(function(searchTypeValue) {
  var searchTarget = callbackUrl +"/documents/search"+"?"+searchTypeBackeEndProp+"="+searchTypeValue;
  var eventReq = new Request(searchTarget, "GET", headers);
  var state = {"searchTypeValue":searchTypeValue,
               "searchTypeRequestProp":searchTypeRequestProp,
               "searchTypeBackeEndProp":searchTypeBackeEndProp,
               "fieldName":fieldName};
  httpClient.send(eventReq, generateCallback(state));
});


Not sure if this would be suitable for you or not.

View solution in original post

3 REPLIES 3

This JS will send out parallel JS calls, but it will wait. It does not defer the waiting to a separate policy.

function generateCallback(state) {
  return function(response, error) {
    ....
  };
}


searchRequests.forEach(function(searchTypeValue) {
  var searchTarget = callbackUrl +"/documents/search"+"?"+searchTypeBackeEndProp+"="+searchTypeValue;
  var eventReq = new Request(searchTarget, "GET", headers);
  var state = {"searchTypeValue":searchTypeValue,
               "searchTypeRequestProp":searchTypeRequestProp,
               "searchTypeBackeEndProp":searchTypeBackeEndProp,
               "fieldName":fieldName};
  httpClient.send(eventReq, generateCallback(state));
});


Not sure if this would be suitable for you or not.

Thanks for your response. It sends out parallel requests but not able to mashup the responses. For example, I am invoking mock service 3 times (just for demo) and trying to combine the responses in following code.

 
function handleRequest () {
    var  headers = {
         apikey: "MYKEY"//context.getVariable("request.header.apikey")
        };
    
    
    var url = "https://mocktarget.apigee.net/json";
   
    var result = [];
    [1,2,3].forEach(function(state) {
        print ("State " + state + " started...");
        var req = new Request(url, "GET", headers);
        result.push(httpClient.send(req, generateCallback(state)));
        print ("State " + state + " completed...");
        
        
    });
    
    context.setVariable("response.header.Content-Type", "application/json");
    context.setVariable("response.content",JSON.stringify(result));
    print ("COMPLETED");
}

function generateCallback(state) {
  return function(response, error) {
        var respContent;
	    print("State: " + state + ", status==" + response.status);
		if (response.status==200) {
			respContent = JSON.parse(response.content);
			print (JSON.stringify(respContent));
		
		}
		return respContent;
  };
}

handleRequest();
<br>

But I got empty response as below as httpClient will not wait for the response.

[ {}, {}, {} ]

To resolve this I created two JS policies -

HTTP Call out: Invoke request & save result in multiple flow variables

AssmbleResponse: Read flow variables and combine responses

HTTP callout:

 
function handleRequest () {
    var  headers = {
         apikey: "MYKEY"//context.getVariable("request.header.apikey")
    };
    
    var url = "https://mocktarget.apigee.net/json";
    print(url);
    print("headers==" + JSON.stringify(headers));
    
    [1,2,3].forEach(function(state) {
        print ("State " + state + " started...");
        var req = new Request(url, "GET", headers);
        httpClient.send(req, generateCallback(state));
        print ("State " + state + " completed...");
    });
    print ("COMPLETED");
}

function generateCallback(state) {
  return function(response, error) {
        var respContent;
	    print("State: " + state + ", status==" + response.status);
		if (response.status==200) {
			respContent = JSON.parse(response.content);
		
		}
		context.setVariable("Respone" + state, respContent);
		return true;
  };
}

handleRequest()
<br>

AssmbleResponse:

var result = [];


[1,2,3].forEach(function(state) {
    var res =  context.getVariable("Respone" + state);
    result.push(res);
});


context.setVariable("response.header.Content-Type", "application/json");
context.setVariable("response.content",JSON.stringify(result));

This works fine and I think it will resolve our requirement.

Please let me know if there any better approach than this.

Yes, that approach is fine. That will work well.

Keep in mind the timeLimit for the JS step. Depending on the behavior of the upstream systems you may need to expand the timeLimit for this step from the default of 200ms.