1.3.3 Promise与async/await
CompletableFuture还实现了另一个接口——CompletionStage,前面我们用到的thenAccept类似的方法也都是这个接口的API。从定义和功能来看,CompletionStage是一个Promise。
那么Promise又是什么呢?按照Promises/A+(https://promisesaplus.com/)给出的定义,Promise是一个异步任务,它存在挂起、完成、拒绝三个状态,当它处在完成状态时,结果通过调用then方法的参数进行回调;出现异常拒绝时,通过catch方法传入的参数来捕获拒绝的原因。
从ECMAScript 6开始,JavaScript就已经支持Promise了,我们先来看之前的例子怎么用Promise来实现,如代码清单1-19所示。
代码清单1-19 使用Promise批量获取图片(JavaScript)
function bitmapPromise(url) { return new Promise((resolve, reject) => { try { download(url, resolve) } catch (e) { reject(e) } }) } const urls = ...; // 省略url的获取 Promise.all(urls.map(url => bitmapPromise(url))) .then(bitmaps => console.log(bitmaps)) .catch(e => console.error(e))
我们注意到,JavaScript的Lambda语法更接近Java 8。以url=>bitmapPromise(url)为例,url是参数,bitmapPromise(url)是表达式体,由于只有一行,因此它的返回值也是Lambda表达式的返回值。
我们通过bitmapPromise函数创建Promise实例,后者接收一个Lambda表达式,这个Lambda表达式有两个参数,resolve和reject,分别对应完成和拒绝状态的回调。其中resolve会作为参数传给download函数,在图片获取完成之后回调。
Promise.all会将多个Promise整合到一起,这与我们前面为整合CompletableFuture而定义的List<CompletableFuture<T>>.allOf如出一辙。最终我们得到一个新的Promise,它的结果是整合了前面所有bitmapPromise函数返回的结果的bitmaps,因此我们在then当中传入的Lambda表达式就是用来处理消费这个bitmaps的。
这样看起来很不错,达到了与CompletableFuture同样的效果,不过还可以更简洁。我们可以通过async/await将上面的代码进一步简化,如代码清单1-20所示。
代码清单1-20 使用async/await
async function main() { try { const bitmaps = await Promise.all(urls.map(url => bitmapPromise(url))); console.log(bitmaps); } catch (e) { console.error(e); } }
我们给整个逻辑的外部函数加上了async关键字,这样就可以在异步调用返回Promise的位置加上await,这个语法糖可以把前面的then和catch调用转换成我们熟悉的同步调用语法。这样看上去逻辑是否清楚多了呢?
当然,由于每个bitmapPromise函数返回的都是Promise,因此我们也可以对每一个Promise进行await,如代码清单1-21所示。
代码清单1-21 循环中使用async/await
async function main() { try { const promises = urls.map(url => bitmapPromise(url)); const bitmaps = []; for (const promise of promises) { bitmaps.push(await promise) } console.log(bitmaps); } catch (e) { console.error(e); } }
async和await很好地兼顾了异步任务执行和同步语法结构的需求,凡是有过回调开发经验的开发者都可以很容易理解它的内在含义。以下几个较为流行的语言也支持这一特性:
·JavaScript ES 2016(ES7)
·C#5.0
·Python 3.5
·Rust 1.39.0
而本书的主角Kotlin对async/await的支持稍微有些不同,它没有引入这两个关键字就实现了这一功能。具体如何做到这一点,我们将在后面详细介绍。
注意 本书在介绍协程概念时,会涉及与其他语言的对比,例如常见的JavaScript、Go等。这些语言都偏向命令式风格,语法上与Java、Kotlin相近,通过我们的剖析,读者应该可以大致理解它们的执行过程。这些内容仅做了解即可。