1.3.2 CompletableFuture
从某种意义上来讲,通过阻塞当前调用来等待异步结果,让异步的逻辑变得不像“异步”了,是因为我们还得同步地等待结果。因此JDK 1.8又新增了一个CompletableFuture类,它实现了Future接口,通过它我们可以拿到异步任务的结果,此外,它还有很多更好用的方法。我们先将之前的代码改造成CompletableFuture的形式,见代码清单1-15。
代码清单1-15 返回CompletableFuture的异步函数
fun bitmapCompletableFuture(): CompletableFuture<Bitmap> = CompletableFuture.supplyAsync { ... // 省略获取图片的逻辑 }
使用CompletableFuture来获得结果就显得更巧妙了,如代码清单1-16所示。
代码清单1-16 整合多个CompletableFuture的结果
urls.map { bitmapCompletableFuture(it) }.let { futureList -> CompletableFuture.allOf(*futureList.toTypedArray()) .thenApply { futureList.map { it.get() } } }.thenAccept { bitmaps -> ... // 省略处理图片 }
同样,我们通过urls得到了bitmaps,并且thenAccept只会在结果就绪时回调,因此这段逻辑也不会阻塞整体代码的执行流程。
当然,CompletableFuture的API还不是十分好用,本例当中我们为了整合多个CompletableFuture<Bitmap>,要先通过allOf构造出一个CompletableFuture<Void>,后者会在所有的Bitmap都就绪时回调它自己的thenApply,我们再通过get函数一一拿到对应的Bitmap。这个过程完全可以提供一个API供开发者使用,我们可以使用Kotlin的扩展函数来试着提供这个实现,如代码清单1-17所示。
代码清单1-17 整合CompletableFuture结果的API
fun <T> List<CompletableFuture<T>>.allOf(): CompletableFuture<List<T>> { return CompletableFuture.allOf(*this.toTypedArray()) .thenApply { this.map { it.get() } } }
这样我们前面的代码就可以进一步简化了,如代码清单1-18所示。
代码清单1-18 简化的CompletableFuture调用
urls.map { bitmapCompletableFuture(it) }.allOf().thenAccept { bitmaps -> ... // 省略处理图片 }
与直接使用Future不同,get函数的调用仍然在CompletableFuture提供的异步调用环境当中,不会阻塞主调用流程。
CompletableFuture算是JDK提供的很好用的异步API,它解决了异步结果阻塞主调用流程的问题,但却让结果的获取脱离了主调用流程。那么,有没有既不阻塞又不脱离主调用流程的办法呢?