JavaScript 语言的执行环境是单线程的,异步编程对于 JavaScript 来说必不可少。JavaScript 传统异步解决方案次要是通过回调函数,而回调函数最大的问题就是 Callback Hell。所以 ES6 规范提供的 Promise 对象,专门用于解决异步编程的问题。

而 Java 语言是一个反对多线程的语言,语法以同步为主,在理论开发中很少须要用到大量的异步编程。然而要想谋求更高的性能,异步通常是更好的抉择。例如 Servlet 3 的异步反对、Spring 5 提供的 Spring WebFlux 等,都是为了谋求更高的性能。和 JavaScript 一样,传统的 Callback 形式解决 Java 异步也会有 Callback Hell 问题,所以在 Java 8 中新增了和 ES6 的 Promise 相似的对象: java.util.concurrent.CompletableFuture

创建对象

创立 Promise

在 JavaScript 中创立 Promise 对象:

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

例如,jQuery 传统的应用回调函数的 ajax 写法是这样的:

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

如果要把它封装一下,返回一个 Promise 对象,能够这样来写:

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

调用这个封装好的 promisifyAjax 办法:

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

创立 CompletableFuture

在 Java 中创立 CompletableFuture 对象:

CompletableFuture<String> completableFuture = new CompletableFuture<>();if (success) {    completableFuture.complete("工作胜利");} else {    completableFuture.completeExceptionally(new RuntimeException("工作失败"));}

CompletableFuture 能够应用泛型,用于指定这个工作胜利后返回的后果的数据类型。

这里能够用 OkHttp 的异步申请来实战应用一下 CompletableFuture。OkHttp 异步申请的官网示例中应用的异步实现形式是基于 Callback 回调的形式:

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

上面将这个异步申请封装为一个返回 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());                }            }        }    });}

应用封装好的 asyncRequest() 办法:

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

能够看到这个写法简直和 ES6 Promise 写法一样。

根本应用

下面基于 jQuery.ajax() 函数封装并返回 Promise 对象,是为了学习 Promise 对象是如何创立的。实际上,曾经有十分优良的开源我的项目 Axios,它是一个基于 Promise 的 HTTP 客户端,既反对浏览器 Ajax,又反对 Node.js:

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

同样,在 Java 11 版本中新增了 java.net.http.HttpClient,纯天然反对 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);}

then 的链式调用

ES6 Promise 的 then 办法的返回值同样是一个 Promise,所以能够链式调用:

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 可通过 thenCompose 办法来实现多个 CompletableFuture 链式调用:

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

工具办法

Promise 中的工具办法:

  • Promise.all() 用于将多个 Promise 包装成一个新的 Promise,相当于让所有工作同时进行,当所有工作都胜利后,新的 Promise 才会变为胜利状态,只有有一个工作失败,新的 Promise 就会变为失败状态

    // 同时执行 3 个异步工作const allPromise = Promise.all([promise1, promise2, promise3]);allPromise.then(([result1, result2, result3]) => {    // 3 个异步工作全副胜利后,这里能够拿到所有工作的后果}).catch(err => {    // 只有有一个工作失败,最终后果就是失败});
  • Promise.race() 用于让多个工作同时进行,只有有一个工作执行实现后(无论胜利还是失败),返回的新的 Promise 就会跟随着变更状态

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

CompletableFuture 中也提供了上述相似的静态方法:

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

其中,CompletableFuture 的 allOf() 办法相似于 Promise.all()anyOf() 办法相似于 Promise.race()

其中有一个区别是 CompletableFuture 的 allOf() 办法返回的是一个 CompletableFuture<Void>,也就是拿不到异步工作的执行后果。如果想要像 Promise.all() 一样拿到每一个工作的执行后果,能够对这个办法再进行一下封装:

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

参考文档

  • java.util.concurrent.CompletableFuture
  • java.net.http.HttpClient
  • Promise
  • Axios

关注我的公众号