文章目录
  1. 1. 基本用法
    1. 1.1. ES6 Promise
    2. 1.2. Java 8 CompletableFuture
  2. 2. 使用案例
  3. 3. then 的链式调用
  4. 4. 工具方法
  5. 5. 参考文档

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{
// 将 Promise 更新为成功状态
resolve(data);
    },
    error: function(jqXHR, textStatus, error{
// 将 Promise 更新为失败状态
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) {
// 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 的方法:

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

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) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle 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) {
// 把第一个请求的结果作为第二个请求的参数,并且返回一个新的 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 链式调用:

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 作为下一个 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 就会变为失败状态

    1
    2
    3
    4
    5
    6
    7
    // 同时执行 3 个异步任务
    const allPromise = Promise.all([promise1, promise2, promise3]);
    allPromise.then(([result1, result2, result3]) => {
    // 3 个异步任务全部成功后,这里可以拿到所有任务的结果
    }).catch(err => {
    // 只要有一个任务失败,最终结果就是失败
    });
  • Promise.race() 用于让多个任务同时进行,只要有一个任务执行完成后(无论成功还是失败),返回的新的 Promise 就会跟随着变更状态

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

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

参考文档

文章目录
  1. 1. 基本用法
    1. 1.1. ES6 Promise
    2. 1.2. Java 8 CompletableFuture
  2. 2. 使用案例
  3. 3. then 的链式调用
  4. 4. 工具方法
  5. 5. 参考文档