Skip to content

Executing Requests Asynchronously

Lahiru Sahan Jayasinghe edited this page Jan 5, 2014 · 9 revisions

A request execution is a blocking operation and multiple requests can only be executed sequentially. For asynchronous execution you could rely on your own threading mechanisms or defer to using @Async with an AsyncHandler. This will allow you to execute several requests in parallel and handle the results once the response is available.


####1. Identifying Asynchronous Requests

The @Async annotation can be placed on an endpoint to execute all requests asynchronously or on a specific request definition to selectively identify asynchronous requests.

@Async @Endpoint("https://example.com")
public interface SeussEndpoint {

    @DELETE("/thing1")
    void anAsyncRequest();

    @DELETE("/thing2")
    void anotherAsyncRequest();
}

or...

@Endpoint("https://example.com")
public interface SeussEndpoint {

    @Async @DELETE("/thing1")
    void anAsyncRequest();

    @DELETE("/thing2")
    void notAnAsyncRequest();
}

No response content is expected to be handled for the above requests.


####2. Defining Callbacks for Responses

To process the resulting response of an asynchronous request, an AsyncHandler must be provided as an argument. The method return-type is still inspected for deserialization. The parameterized AsyncHandler type should be assignable from the method return-type.

@Deserialize(JSON)
@Endpoint("https://api.github.com")
public interface GitHubEndpoint {

    @Async @GET("/users/{user}/repos")
    List<Repo> getRepos(@PathParam("user") String user, AsyncHandler<List<Repo>> asyncHandler);
}

getRepos(...) will always return null.


####3. Response Handling Scenarios

The typical scenario is to execute an asynchronous request and handle a successful response. A response is deemed successful if it contains a status code of type 2xx. For such responses the onSuccess(...) callback of an AsyncHandler will be invoked.

gitHubEndpoint.getRepos("sahan", new AsyncHandler<List<Repo>>() {
			
    @Override
    public void onSuccess(HttpResponse response, List<Repo> repos) {
        
        doSomethingUseful(repos);
    }
});

onSuccess(...) is the only callback which is mandated to be overridden.

If the status code is not of type 2xx, the request is considered to have failed and the onFailure(...) callback is invoked.

import org.apache.http.util.EntityUtils;

...

gitHubEndpoint.getRepos("sahan", new AsyncHandler<List<Repo>>() {
			
    @Override
    public void onSuccess(HttpResponse response, List<Repo> repos) {
        
        doSomethingUseful(repos);
    }

    @Override
    public void onFailure(HttpResponse response) {
				
        String details = EntityUtils.toString(response.getEntity());
        logAndAlertFailure(details);
    }
});

If an error occurred while creating the request and executing it or while processing the response, the onError(...) callback will be invoked. This callback will provide you a context-aware InvocationException. Use getContext() to retrieve the InvocationContext which can be used to examine the request information and metadata. If the error occurred while processing the response, an HttpResponse should be available. Invoke hasResponse() to check the availability of an HttpResponse and invoke getResponse() to retrieve it.

gitHubEndpoint.getRepos("sahan", new AsyncHandler<List<Repo>>() {
			
    @Override
    public void onSuccess(HttpResponse response, List<Repo> repos) {
        
        doSomethingUseful(repos);
    }

    @Override
    public void onError(InvocationException errorContext) {
				
        InvocationContext context = errorContext.getContext();
				
        if(errorContext.hasResponse()) {
					
            HttpResponse response = errorContext.getResponse();
            logAndAlertError(context, response);
        }
        else {
					
            logAndAlertError(context);
        }
    }
});

Clearly, the hasResponse() method doubles as an indicator to distinguish between a request creation/execution error vs a response processing error.


####4. Reading Response Headers

Extracting response headers remains the same with asynchronous requests, with the exception that it should be performed within a callback.

@Deserialize(JSON)
@Endpoint("https://api.github.com")
public interface GitHubEndpoint {

    @Async @GET("/users/{user}/repos")
    List<Repo> getRepos(@PathParam("user") String user, 
                        @Header("X-RateLimit-Remaining") StringBuilder header, 
                        AsyncHandler<List<Repo>> asyncHandler);
}

...

final StringBuilder responseHeader = new StringBuilder();

gitHubEndpoint.getRepos("sahan", responseHeader, new AsyncHandler<List<Repo>>() {
			
    @Override
    public void onSuccess(HttpResponse response, List<Repo> repos) {
        
        String rateLimitRemaining = responseHeader.toString();
        doSomethingUseful(repos, rateLimitRemaining);
    }
});