4

The execution environment of the JavaScript language is single-threaded, and asynchronous programming is essential for JavaScript. JavaScript traditional asynchronous solutions mainly use callback functions, and the biggest problem with callback functions is Callback Hell. Therefore, the Promise object provided by the ES6 standard is specifically used to solve the problem of asynchronous programming.

The Java language is a language that supports multi-threading. The syntax is mainly synchronous, and a lot of asynchronous programming is rarely needed in actual development. But if you want to pursue higher performance, asynchronous 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 approach to handle Java asynchrony will also have the Callback Hell problem, so a new object similar to ES6's Promise is added in Java 8: java.util.concurrent.CompletableFuture .

Create an object

Create Promise

Create a Promise object in JavaScript:

const promise = new Promise(function(resolve, reject) {
    if (success){
        // 成功
        resolve(value);  
    } else {
        // 失败
        reject(error);   
    }
});

For example, jQuery's traditional ajax writing method using callback functions is like this:

$.ajax({
    url: "/url",
    success: function(data) {
        // 成功
    },
    error: function(jqXHR, textStatus, error) {
        // 失败
    }
});

If you want to encapsulate it and return a Promise object, you can write it like this:

function promisifyAjax(url) {
    const promise = new Promise(function(resolve, reject) {
        $.ajax({
            url: url,
            success: function(data) {
                // 将 Promise 更新为成功状态
                resolve(data);   
            },
            error: function(jqXHR, textStatus, error) {
                // 将 Promise 更新为失败状态
                reject(error);   
            }
        });
    });
    return promise;
}

Call the encapsulated promisifyAjax method:

promisifyAjax("/url").then(function(data) {
    // 成功
}).catch(function(error) {
    // 失败
});

Create CompletableFuture

Create a CompletableFuture object in Java:

CompletableFuture<String> completableFuture = new CompletableFuture<>();
if (success) {
    completableFuture.complete("任务成功");
} else {
    completableFuture.completeExceptionally(new RuntimeException("任务失败"));
}

CompletableFuture can use generics to specify the data type of the result returned after the task is successful.

Here you can use OkHttp's asynchronous request to actually use CompletableFuture. The official example of is based on the callback method:

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();
    
    client.newCall(request).enqueue(new Callback() {
        @Override 
        public void onFailure(Call call, IOException e) {
            // HTTP 请求异常
            e.printStackTrace();  
        }
        
        @Override 
        public void onResponse(Call call, Response response) throws IOException {
            try (ResponseBody responseBody = response.body()) {
                if (!response.isSuccessful()) { 
                    // 响应状态码异常
                    throw new IOException("Unexpected code " + response);
                } else {
                    // 成功
                    System.out.println(responseBody.string());  
                }
            }
        }
    });
}

The following will encapsulate this asynchronous request as a method that returns CompletableFuture:

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 请求异常
            completableFuture.completeExceptionally(e);  
        }
        
        @Override 
        public void onResponse(Call call, Response response) throws IOException {
            try (ResponseBody responseBody = response.body()) {
                if (!response.isSuccessful()) { 
                    // 响应状态码异常
                    completableFuture.completeExceptionally(new IOException("Unexpected code " + response));
                } else {   
                    // 成功
                    completableFuture.complete(responseBody.string());
                }
            }
        }
    });
}

Use the encapsulated asyncRequest() method:

asyncRequest("/url").thenAccept(responseText -> {
    // 成功
}).exceptionally(e -> {
    // 失败
});

You can see that this writing method is almost the same as ES6 Promise writing.

Basic use

The above is based on the jQuery.ajax() function to encapsulate and return the Promise object, in order to learn how the Promise object is created. In fact, there is already a very good open source project Axios , which 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);
    });

java.net.http.HttpClient was added in the Java 11 version, and the natural support CompletableFuture :

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;
    });

    // 防止主线程结束后程序停止
    Thread.sleep(Integer.MAX_VALUE);
}

chain call of then

The return value of ES6 Promise's then method is also a Promise, so it can be called in a chain:

axios.get('/request1')
    .then(function (response) {
        // 把第一个请求的结果作为第二个请求的参数,并且返回一个新的 Promise 作为下一个 then 的结果
        const newPromise = axios.get('/request2?param=' + response);
        return newPromise;
    })
    .then(function (response) {
        // 输出第二个请求的结果
        console.log(response);
    })
    .catch(function (error) {
        console.log(error);
    });

Java CompletableFuture can implement multiple CompletableFuture chain calls through the thenCompose 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 -> {
        // 把第一个请求的结果作为第二个请求的参数
        HttpRequest request2 = HttpRequest.newBuilder()
                .uri(URI.create("http://foo.com/?param=" + response.body()))
                .build();
        // 这里必须返回一个新的 CompletableFuture 作为下一个 then 的结果
        CompletableFuture<HttpResponse<String>> responseFuture2 = client.sendAsync(request2, HttpResponse.BodyHandlers.ofString());
        return responseFuture2;
    }).thenAccept(response -> {
        // 输出第二个请求的结果
        System.out.println(response);
    }).exceptionally(e -> {
        e.printStackTrace();
        return null;
    });

    // 防止主线程结束后程序停止
    Thread.sleep(Integer.MAX_VALUE); 
}

Tool method

Tool method in Promise:

  • Promise.all() used to wrap multiple Promises into a new Promise, which is equivalent to making all tasks go on at the same time. When all tasks are successful, the new Promise will become a success state. As long as one task fails, the new Promise will be To fail

    // 同时执行 3 个异步任务
    const allPromise = Promise.all([promise1, promise2, promise3]);
    allPromise.then(([result1, result2, result3]) => {
        // 3 个异步任务全部成功后,这里可以拿到所有任务的结果
    }).catch(err => {
        // 只要有一个任务失败,最终结果就是失败
    });
  • Promise.race() used to allow multiple tasks to be performed at the same time. As long as one task is completed (regardless of success or failure), the new Promise returned will follow the change status

    // 发起 HTTP 请求,5 秒内没有响应则超时失败
    Promise.race([
        httpGet('http://example.com/file.txt'),
        delay(5000).then(function () {
            throw new Error('timeout');
        })
    ])

CompletableFuture also provides the above-mentioned similar static method:

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

Among them, the allOf() method of Promise.all() , and the anyOf() method is similar to Promise.race() .

One difference is that the allOf() method of CompletableFuture returns a CompletableFuture<Void> , that is, the execution result of the asynchronous task cannot be obtained. If you want to Promise.all() , you can encapsulate this method again:

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

扫码关注


叉叉哥
3.8k 声望60 粉丝