全栈笔记(一)Promise对比CompleteableFuture

前言

此篇是开启【全栈笔记】系列的第一篇文章,记录软件开发过程中遇到的相似技术点来做个总结。今天我们要对比的是 CompleteableFuture 与 Promise。本文适合有一定 Java 与 JavaScript 基础的同学阅读。

简单对比

Promise

1
2
3
4
let p = new Promise((resolve, reject) => {
Math.random() > 0.5 ? resolve("执行成功") : reject("执行失败")
})
p.then((res) => console.log(res)).catch((res) => console.log(res))

CompleteableFuture

1
2
3
4
5
6
7
8
9
10
11
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
if (ThreadLocalRandom.current().nextDouble() <= 0.5) {
throw new RuntimeException("执行失败");
}
return "执行成功";
});
cf.thenAccept(System.out::println);
cf.exceptionally((action) -> {
System.out.println(action.getMessage());
return null;
});

上面两段代码相似度很高,是不同语言的异步编程。Promise 对应 CompletableFuture,p.then 对应 cf.thenAccept,p.catch 对应 cf.exceptionally。
这里再列举一个相似的用法。

Promise.race

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const p1 = new Promise((resolve) => {
// 等待3秒执行
setTimeout(() => {
resolve("p1")
}, 3000)
})
const p2 = new Promise((resolve) => {
// 等待2秒执行
setTimeout(() => {
resolve("p2")
}, 2000)
})
const p3 = new Promise((resolve) => {
// 等待1秒执行
setTimeout(() => {
resolve("p3")
}, 1000)
})
Promise.race([p1, p2, p3]).then((res) => console.log("race", res))
// 输出 p3

CompletableFuture.anyOf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void sleepSeconds(int seconds) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(3);
return "cf1";
});
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(2);
return "cf2";
});
CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(() -> {
sleepSeconds(1);
return "cf3";
});
System.out.println(CompletableFuture.anyOf(cf1, cf2, cf3).join());
// 输出 cf3

Promise.race方法只要p1、p2、p3之中有任何一个实例率先执行成功,p的状态就跟着改变。就像他的方法名race一样,p1,p2,p3赛跑返回最先执行完(最先跑过终点)的值。CompletableFuture.anyOf和Promise.race效果相同。

Promise.all和CompletableFuture.allOf也是相同的。只有等p1,p2,p3全部执行完之后才返回他们三者的值。

异步与多线程

什么是异步

先来看下同步的定义,「一定要等任务执行完了,得到结果,才执行下一个任务」。异步就是「不等任务执行完,直接执行下一个任务」。JS运行在浏览器中是单线程的,注意浏览器是多线程。有些同学会说异步是同时做两件事,但其实 JS 引擎不会这样,异步意味着不等待任务结束,并没有强制要求两个任务「同时」进行。

强调的是不等待,通常异步都是与同步做比较

什么是多线程

多线程是指每条线程可以并行「同时」执行不同的任务。

强调的是同时执行

发现在实际开发中很多同学会混淆异步与多线程的概念。Promise与CompleteableFuture都属于异步编程,但是他们实现异步的方式不同,Promise是Event Loop的运行机制实现的异步,CompleteableFuture是用多线程实现的异步。异步是一种目的。多线程是实现目的的一种方式。

CompleteableFuture的thenXXX方法中打印线程的名称,有时会发现他是在另一个线程中执行的。

总结

promise作为异步操作的API,给前端开发带来了不少便利,有效的解决了回调地狱的问题,更重要的是让代码变的更加优雅。CompletableFuture是JDK1.8的时候加入的,在Future的基础上,增加了通知回调的功能(实现CompletionStage接口)。很多RPC框架中都有对CompletableFuture的支持,因为CompletableFuture是Java原生支持的有更好的性能和兼容,更重要的在是业务逻辑中没有代码侵入。