Asynchronous Callouts from Visualforce Pages with Continuations

In this blog, I am going to explain how to use salesforce  Continuations Asynchronous Callouts from the visualforce page.Continuations  Apex feature that allows you to escape the limit of ten concurrent long-running callouts. Asynchronous Callouts work through a pattern called a Continuation; your controller creates its HTTP request as it normally would, but, instead of firing it off there and then, you create a Continuation object, pass the HTTP request and an Apex callback method to the Continuation, and return it to the platform for processing.Use asynchronous callouts to make long-running requests from a Visualforce page to an external Web service and process responses in callback methods. Asynchronous callouts that are made from a Visualforce page don’t count toward the Apex limit of 10 synchronous requests that last longer than five seconds. As a result, you can make more long-running callouts and you can integrate your Visualforce pages with complex back-end assets.An asynchronous callout is a callout that is made from a Visualforce page for which the response is returned through a callback method. An asynchronous callout is also referred to as a continuation.This diagram shows the execution path of an asynchronous callout, starting from a Visualforce page. A user invokes an action on a Visualforce page that requests information from a Web service (step 1). The app server hands the callout request to the Continuation server before returning to the Visualforce page (steps 2–3). The Continuation server sends the request to the Web service and receives the response (steps 4–7), then hands the response back to the app server (step 8). Finally, the response is returned to the Visualforce page (step 9).

Diagram for the execution flow of a continuation

Using Continuation 

Step1: Action Method

To use asynchronous callouts, create a Continuation object in an action method of a controller, and implement a callback method.To invoke an asynchronous callout, call the external service by using a Continuation instance in your Visualforce action method. When you create a continuation, you can specify a timeout value and the name of the callback method. For example, the following creates a continuation with a 60-second timeout and a callback method name of processResponse.

Continuation cont = new Continuation(60);
cont.continuationMethod = 'processResponse';

Next, associate the Continuation object to an external callout.

String requestLabel = cont.addHttpRequest(request);

The method that invokes the callout (the action method) must return the Continuation object to instruct Visualforce to suspend the current request after the system sends the callout and waits for the callout response. The Continuation object holds the details of the callout to be executed.Here is the complete code to invoke from the action.

 public Object startRequest() {
        // Create continuation with a timeout
        Continuation con = new Continuation(40);
        // Set callback method
        con.continuationMethod='processResponse';
        
        // Create callout request
        HttpRequest req = new HttpRequest();
        req.setMethod('GET');
        req.setEndpoint(LONG_RUNNING_SERVICE_URL);
        
        // Add callout request to continuation
        this.requestLabel = con.addHttpRequest(req);
        
        // Return the continuation
        return con;  
    }

Step 2: Defining a Callback Method

The response is returned after the external service finishes processing the callout. You can specify a callback method for asynchronous execution after the callout returns. This callback method must be defined in the controller class where the callout invocation method is defined. You can define a callback method to process the returned response, such as retrieving the response for display on a Visualforce page as shown below .

 // Callback method 
    public Object processResponse() {   
        // Get the response by using the unique label
        HttpResponse response = Continuation.getResponse(this.requestLabel);
        // Set the result variable that is displayed on the Visualforce page
        //this.result = response.getBody();
        this.jsonParserResult = (List<JSONAlbums>) System.JSON.deserialize(response.getBody(), List<JSONAlbums>.class);
        // Return null to re-render the original Visualforce page
        return null;
    }

 

Example code

In the following example application, the button action is implemented in an Apex controller method. The action method creates a Continuation and returns it. After the request is sent to the service, the Visualforce request is suspended. The user must wait for the response to be returned before proceeding with using the page and invoking new actions. When the external service returns a response, the Visualforce request resumes and the page receives this response.

 

Simple Apex class

public class ContinuationRestCall {
    // Unique label corresponding to the continuation
    public String requestLabel;
    // Result of callout
    public String result {get;set;}
    // Callout endpoint as a named credential URL 
    // or, as shown here, as the long-running service URL
    private static final String LONG_RUNNING_SERVICE_URL = 'https://jsonplaceholder.typicode.com/users/1/albums';
    public List<JSONAlbums> jsonParserResult {get;set;}
    // Action method
    public Object startRequest() {
        // Create continuation with a timeout
        Continuation con = new Continuation(40);
        // Set callback method
        con.continuationMethod='processResponse';
        
        // Create callout request
        HttpRequest req = new HttpRequest();
        req.setMethod('GET');
        req.setEndpoint(LONG_RUNNING_SERVICE_URL);
        
        // Add callout request to continuation
        this.requestLabel = con.addHttpRequest(req);
        
        // Return the continuation
        return con;  
    }
    
    // Callback method 
    public Object processResponse() {   
        // Get the response by using the unique label
        HttpResponse response = Continuation.getResponse(this.requestLabel);
        // Set the result variable that is displayed on the Visualforce page
        //this.result = response.getBody();
        this.jsonParserResult = (List<JSONAlbums>) System.JSON.deserialize(response.getBody(), List<JSONAlbums>.class);
        // Return null to re-render the original Visualforce page
        return null;
    }
    
    public class JSONAlbums {
        public Integer userId{get;set;}
        public Integer id{get;set;}
        public String title{get;set;}
    }
    
    
}

 

Visuaforce page

<apex:page controller="ContinuationRestCall" showChat="false" showHeader="false" >
    
    <apex:form >
        <!-- Invokes the action method when the user clicks this button. -->
        <apex:commandButton action="{!startRequest}" 
                            value="Start Request" reRender="result" /> 
    </apex:form>
    
    <apex:pageBlock id="result"  title="Process for Using Asynchronous Callouts">
        <apex:pageBlockSection  columns="1">
            
            <apex:pageBlockTable value="{!jsonParserResult}" var="res" >
                <apex:column value="{!res.userId}"  headerValue="userId"/>
                <apex:column value="{!res.id}"  headerValue="id"/>
                <apex:column value="{!res.title}"  headerValue="title"/>
            </apex:pageBlockTable>
        </apex:pageBlockSection>        
    </apex:pageBlock>
</apex:page>
@isTest
private class ContinuationRestCall_Test {
    public static testmethod void testWebService() {
        
        ContinuationRestCall controller = new ContinuationRestCall();
        // Invoke the continuation by calling the action method
        Continuation conti = (Continuation)controller.startRequest();
        
        // Verify that the continuation has the proper requests
        Map<String, HttpRequest> requests = conti.getRequests();
        system.assert(requests.size() == 1);
        system.assert(requests.get(controller.requestLabel) != null);
        
        // Perform mock callout 
        // (i.e. skip the callout and call the callback method)
        HttpResponse response = new HttpResponse();
        response.setBody('Mock response body');   
        // Set the fake response for the continuation     
        Test.setContinuationResponse(controller.requestLabel, response);
        // Invoke callback method
        Object result = Test.invokeContinuationMethod(controller, conti);
        // result is the return value of the callback
        System.assertEquals(null, result);
        // Verify that the controller's result variable
        //   is set to the mock response.
        System.assertEquals('Mock response body', controller.result);
    }
    
}

 

Continuation Status Code 

  • 2000: The timeout was reached, and the server didn’t get a chance to respond.
  • 2001: There was a connection failure.
  • 2002: Exceptions occurred.
  • 2003: The response hasn’t arrived (which also means that the Apex asynchronous callout framework hasn’t resumed).
  • 2004: The response size is too large (greater than 1 MB).

Limitation 

  • You can make up to three asynchronous callouts in a single continuation.
  • Asynchronous callouts are supported only through a Visualforce page. Making an asynchronous callout by invoking the action method outside a Visualforce page, such as in the Developer Console, isn’t supported.
  • Asynchronous callouts are available for Apex controllers and Visualforce pages saved in version 30.0 and later. If JavaScript remoting is used, version 31.0 or later is required.
  • This Continuation pattern does not allow you to make DML operations before the web service is invoked.
  • The server has to return within 2 minutes, or there will be a timeout