Promise和async/await提供异步并发能力,是标准的JS异步语法。异步代码会被挂起并在之后继续执行,同一时间只有一段代码执行,适用于单次I/O任务的场景开发,例如一次网络请求、一次文件读写等操作。无需另外启动线程执行。
异步语法是一种编程语言的特性,允许程序在执行某些操作时不必等待其完成,而是可以继续执行其他操作。
一、基本用法
1.1鸿蒙
async fetchData() {
const value1 = await PreferenceManager.getInstance().getValue<xxx>(AccountModel.xxx)
const value2 = await PreferenceManager.getInstance().getValue<xxx>(AccountModel.xx)
const value3 = await PreferenceManager.getInstance().getValue<xxx>(AccountModel.xx)
}
1.2安卓
fun fetchData() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
Thread.sleep(1000)
Log.d("bai", "1111111111")
}
withContext(Dispatchers.IO) {
Thread.sleep(1000)
Log.d("bai", "2222222")
}
withContext(Dispatchers.IO) {
Thread.sleep(1000)
Log.d("bai", "333333")
}
}
}
1.3对比
用法上相差不大,鸿蒙更简洁一些。
鸿蒙直接给函数标记async,然后在函数内可以await阻塞耗时操作返回再往下执行。
安卓的协程则需要在协程作用域内使用,函数需要用suspend标记,也需要切线程来执行耗时操作。
需要注意的是鸿蒙如果想要调用async函数的await,需要该函数也是async。比如上面的fetchData,如果有个loadData函数想要调用fetchData的await,那么loadData也必须是async标记,如下:
async loadData(){
await this.fetchData()
}
如果loadData函数不适合用async标记,那么可以改成用回调的形式使用
loadData(){
this.fetchData().then(()=>{
})
}
到这里,其实可以看出来async、await解决的其中一个问题:回调地狱。
试想一下,如果有个场景是先请求A,用A请求B,用B请求C,用C请求D.....用then的话,需要嵌套多少层回调。
如果用async、await只是多几行await罢了
async loadData() {
const value1 = await this.fetchData(0)
const value2 = await this.fetchData(value1)
const value3 = await this.fetchData(value2)
const value4 = await this.fetchData(value3)
}
二、原理浅析
2.1鸿蒙的async、await
由于没看到鸿蒙的源码,鸿蒙的async、await看起来又是js的,因此套用js的原理。
async函数返回的是一个promise对象,通过await去取promise的值。
在 JavaScript 的运行时环境中,基于事件循环(Event Loop)机制来处理异步任务。await 暂停 async 函数执行时,实际上是将后续代码的执行权暂时交出去,等待对应的 Promise 进入微任务队列(Promise 的 then、catch 等回调在成功或失败后会被添加到微任务队列中)并执行完微任务队列中的相关任务后,才会恢复 async 函数的执行。
2.2安卓的协程
安卓的协程由于有源码可以看,因此相对比较清晰。
总的来说协程有两个比较重要的元素:续体continuation和状态机。
续体用来回调恢复,保存上下文。状态机用来恢复时控制继续执行哪些逻辑。
suspend方法在编译成java代码时会塞一个Continuation续体参数给对应函数(其实通过查看源码retrofit对协程的支持,能看到retrofit就是通过代理函数的最后一个参数拿到的continuation)
每当代码执行到挂起点时,函数直接return返回,并且把续体传递给下一个挂起函数,相当于本函数暂时执行完了,也就不会阻塞当前线程如主线程了。
续体可以简单理解成回调,包含了上下文的回调,等挂起函数执行完成后通过续体把上一个函数唤起继续执行,状态机进入下一个状态(如果没有挂起点就可以返回结果了)。
上图的lable是记录状态的,默认0,执行完成后自增1,遇到挂起点直接返回,等continuation的resume唤醒,执行下一个状态。
用安卓的retrofit对协程支持的源码也能比较明显的看出来协程续体的作用
续体其实可以理解成回调,resume对应onSuccess,resumeWithException对应onFaile
2.3对比
从安卓的协程对比鸿蒙的promise,看起来实现的效果是类似的,特别是KMP(kotlin跨端方案)已经有类似的鸿蒙跨端方案,把kotloin的协程转换成鸿蒙的promise。
但是原理应该不一样。安卓协程是在主线程上不能做耗时操作,如果执行了thread.sleep(6000),就会ANR,正确做法需要切到io线程操作。
对应鸿蒙的场景直接耗时操作就没发现出问题,可能是因为js本身单线程微队列的实现原理不一样,这块等后面深入再探究。