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
。
基本用法
ES6 Promise
在 JavaScript 中创建 Promise 对象:
1 2 3 4 5 6 7 8 9
| const promise = new Promise(function(resolve, reject) { if (success){ resolve(value); } else { reject(error); } });
|
例如,jQuery 传统的使用回调函数的 ajax 写法是这样的:
1 2 3 4 5 6 7 8 9
| $.ajax({ url: "/url", success: function(data) { }, error: function(jqXHR, textStatus, error) { } });
|
如果要把它封装一下,返回一个 Promise 对象,可以这样来写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function promisifyAjax(url) { const promise = new Promise(function(resolve, reject) { $.ajax({ url: url, success: function(data) { resolve(data); }, error: function(jqXHR, textStatus, error) { reject(error); } }); }); return promise; }
|
调用这个封装好的 promisifyAjax
方法:
1 2 3 4 5
| promisifyAjax("/url").then(function(data) { }).catch(function(error) { });
|
Java 8 CompletableFuture
在 Java 中创建 CompletableFuture 对象:
1 2 3 4 5 6
| CompletableFuture<String> completableFuture = new CompletableFuture<>(); if (success) { completableFuture.complete("任务成功"); } else { completableFuture.completeExceptionally(new RuntimeException("任务失败")); }
|
CompletableFuture 可以使用泛型,用于指定这个任务成功后返回的结果的数据类型。
这里可以用 OkHttp 的异步请求来实战使用一下 CompletableFuture。OkHttp 异步请求的官方示例中使用的异步实现方式是基于 Callback 回调的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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) { 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 的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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) { 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()); } } } });
return completableFuture; }
|
使用封装好的 asyncRequest()
方法:
1 2 3 4 5
| asyncRequest("/url").thenAccept(responseText -> { }).exceptionally(e -> { });
|
可以看到这个写法几乎和 ES6 Promise 写法一样。
Java 中还可以使用 Runnable、Supplier 快速创建 CompletableFuture 对象,两者区别在于 Supplier 有执行结果,而 Runnable 没有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
CompletableFuture<Void> f1 = CompletableFuture.runAsync(new Runnable() { @Override public void run() { doSomething(); } });
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { String res = doSomething(); return res; } });
|
使用案例
上面基于 jQuery.ajax()
函数封装并返回 Promise 对象,是为了学习 Promise 对象是如何创建的。实际上,无论是 ES6 中的 Promise 还是 Java 8 中的 CompletableFuture,都是被广泛认可的标准 API,已经被大量应用。
例如开源项目 Axios,它是一个基于 Promise 的 HTTP 客户端,既支持浏览器 Ajax,又支持 Node.js:
1 2 3 4 5 6 7 8 9
| axios.get('/user?ID=12345') .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
|
同样,在 Java 11 版本中新增了 java.net.http.HttpClient
,纯天然支持 CompletableFuture
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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,所以可以链式调用:
1 2 3 4 5 6 7 8 9 10 11 12 13
| axios.get('/request1') .then(function (response) { 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 链式调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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<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 中的工具方法:
CompletableFuture 中也提供了上述类似的静态方法:
1 2
| 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()
一样拿到每一个任务的执行结果,可以对这个方法再进行一下封装:
1 2 3 4 5 6
| 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())); }
|
参考文档