Java 8 completable future vs. ES6 promise

Time:2021-9-23

The execution environment of JavaScript language is single thread, and asynchronous programming is essential for JavaScript. The traditional asynchronous solution of JavaScript is mainly through callback functions, and the biggest problem of callback functions is callback hell. Therefore, the promise object provided by ES6 standard is specially used to solve the problem of asynchronous programming.

Java language is a language that supports multithreading, and its syntax is mainly synchronous. It rarely needs a lot of asynchronous programming in actual development. However, to pursue higher performance, asynchrony is usually a better choice. For example, the asynchronous support of servlet 3 and spring Webflux provided by spring 5 are all in pursuit of higher performance. Like JavaScript, the traditional callback method for handling Java asynchrony will also have the callback hell problem, so an object similar to promise in ES6 is added in Java 8:java.util.concurrent.CompletableFuture

create object

Create promise

Create promise object in javascript:

const promise = new Promise(function(resolve, reject) {
    if (success){
        //Success
        resolve(value);  
    } else {
        //Fail
        reject(error);   
    }
});

For example, the traditional Ajax writing method of jQuery using callback function is as follows:

$.ajax({
    url: "/url",
    success: function(data) {
        //Success
    },
    error: function(jqXHR, textStatus, error) {
        //Fail
    }
});

If you want to encapsulate it and return a promise object, you can write as follows:

function promisifyAjax(url) {
    const promise = new Promise(function(resolve, reject) {
        $.ajax({
            url: url,
            success: function(data) {
                //Update promise to success status
                resolve(data);   
            },
            error: function(jqXHR, textStatus, error) {
                //Update promise to failed status
                reject(error);   
            }
        });
    });
    return promise;
}

Call this encapsulatedpromisifyAjaxmethod:

promisifyAjax("/url").then(function(data) {
    //Success
}).catch(function(error) {
    //Fail
});

Create completable future

To create a completable future object in Java:

CompletableFuture<String> completableFuture = new CompletableFuture<>();
if (success) {
    Completabilefuture.complete ("task succeeded");
} else {
    Completable future. Completeexceptional (New runtimeException ("task failed");
}

Completable future can use generics to specify the data type of the result returned after the task succeeds.

Here, you can use okhttp’s asynchronous request to actually use completable future.Official example of okhttp asynchronous requestThe asynchronous implementation method used in is based on callback:

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/helloworld.txt")
        .build();
    
    client.newCall(request).enqueue(new Callback() {
        @Override 
        public void onFailure(Call call, IOException e) {
            //HTTP request exception
            e.printStackTrace();  
        }
        
        @Override 
        public void onResponse(Call call, Response response) throws IOException {
            try (ResponseBody responseBody = response.body()) {
                if (!response.isSuccessful()) { 
                    //Abnormal response status code
                    throw new IOException("Unexpected code " + response);
                } else {
                    //Success
                    System.out.println(responseBody.string());  
                }
            }
        }
    });
}

The asynchronous request is encapsulated as a method that returns completable future:

public static CompletableFuture<String> asyncRequest(String url) {
    CompletableFuture<String> completableFuture = new CompletableFuture<>();

    Request request = new Request.Builder()
        .url(url)
        .build();
    
    client.newCall(request).enqueue(new Callback() {
        @Override 
        public void onFailure(Call call, IOException e) {
            //HTTP request exception
            completableFuture.completeExceptionally(e);  
        }
        
        @Override 
        public void onResponse(Call call, Response response) throws IOException {
            try (ResponseBody responseBody = response.body()) {
                if (!response.isSuccessful()) { 
                    //Abnormal response status code
                    completableFuture.completeExceptionally(new IOException("Unexpected code " + response));
                } else {   
                    //Success
                    completableFuture.complete(responseBody.string());
                }
            }
        }
    });
}

Use encapsulatedasyncRequest()method:

asyncRequest("/url").thenAccept(responseText -> {
    //Success
}).exceptionally(e -> {
    //Fail
});

You can see that the writing method is almost the same as that of ES6 promise.

Basic use

Above basedjQuery.ajax()Function encapsulates and returns promise objects to learn how promise objects are created. In fact, there are already excellent open source projectsAxios, it is a promise based HTTP client that supports both browser Ajax and node.js:

axios.get('/user?ID=12345')
    .then(function (response) {
        // handle success
        console.log(response);
    })
    .catch(function (error) {
        // handle error
        console.log(error);
    });

Similarly, in the Java 11 version, newjava.net.http.HttpClient, all natural supportCompletableFuture

public static void main(String[] args) throws InterruptedException {
    HttpClient client = HttpClient.newBuilder().build();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://xxx.com/"))
            .build();
    CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
    
    responseFuture.thenAccept(response -> {
        System.out.println(response.body());
    }).exceptionally(e -> {
        System.out.println(e);
        return null;
    });

    //Prevent the program from stopping after the main thread ends
    Thread.sleep(Integer.MAX_VALUE);
}

Chain call of then

The return value of the then method of ES6 promise is also a promise, so it can be called in a chain:

axios.get('/request1')
    .then(function (response) {
        //Take the result of the first request as the parameter of the second request, and return a new promise as the result of the next then
        const newPromise = axios.get('/request2?param=' + response);
        return newPromise;
    })
    .then(function (response) {
        //Output the result of the second request
        console.log(response);
    })
    .catch(function (error) {
        console.log(error);
    });

Java completabilefuture can realize multiple chain calls to completabilefuture through thenpose method:

public static void main(String[] args) throws InterruptedException {
    HttpClient client = HttpClient.newBuilder().build();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://foo.com/"))
            .build();
    CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

    responseFuture.thenCompose(response -> {
        //Take the result of the first request as the parameter of the second request
        HttpRequest request2 = HttpRequest.newBuilder()
                .uri(URI.create("http://foo.com/?param=" + response.body()))
                .build();
        //Here, a new completable future must be returned as the result of the next then
        CompletableFuture<HttpResponse<String>> responseFuture2 = client.sendAsync(request2, HttpResponse.BodyHandlers.ofString());
        return responseFuture2;
    }).thenAccept(response -> {
        //Output the result of the second request
        System.out.println(response);
    }).exceptionally(e -> {
        e.printStackTrace();
        return null;
    });

    //Prevent the program from stopping after the main thread ends
    Thread.sleep(Integer.MAX_VALUE); 
}

Tool method

Tools and methods in promise:

  • Promise.all()It is used to package multiple promises into a new promise, which is equivalent to making all tasks proceed at the same time. When all tasks are successful, the new promise will change to success status. As long as one task fails, the new promise will change to failure status

    //Execute 3 asynchronous tasks at the same time
    const allPromise = Promise.all([promise1, promise2, promise3]);
    allPromise.then(([result1, result2, result3]) => {
        //After all three asynchronous tasks are successful, the results of all tasks can be obtained here
    }).catch(err => {
        //As long as one task fails, the end result is failure
    });
  • Promise.race()It is used to allow multiple tasks to be performed at the same time. As long as one task is completed (whether successful or failed), the returned new promise will follow the change state

    //When an HTTP request is initiated and there is no response within 5 seconds, the timeout fails
    Promise.race([
        httpGet('http://example.com/file.txt'),
        delay(5000).then(function () {
            throw new Error('timeout');
        })
    ])

Similar static methods are also provided in completable future:

static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

Among them, the of completable futureallOf()The method is similar toPromise.all()anyOf()The method is similar toPromise.race()

One of the differences is that of completable futureallOf()Method returns aCompletableFuture<Void>, that is, the execution results of asynchronous tasks cannot be obtained. If you want to be likePromise.all()After getting the execution result of each task, you can encapsulate this method:

public static <T> CompletableFuture<List<T>> all(CompletableFuture<T> ... cfs) {
    return CompletableFuture.allOf(cfs)
            .thenApply(v -> Stream.of(cfs)
                    .map(future -> future.join())
                    .collect(Collectors.toList()));
}

Reference documents

Follow my public account

Java 8 completable future vs. ES6 promise

Recommended Today

Seven Python code review tools recommended

althoughPythonLanguage is one of the most flexible development languages at present, but developers often abuse its flexibility and even violate relevant standards. So PythoncodeThe following common quality problems often occur: Some unused modules have been imported Function is missing arguments in various calls The appropriate format indentation is missing Missing appropriate spaces before and after […]