简断截说,上代码!
举例:
fun login(name:String,pass:String){
HttpMethods.webService.login(name,pass){
override fun onSuccess(bean: LoginBean) {
connect(bean.token)
}
override fun onError(e: Exception) {
}
}
fun connect(token:String){
......//调用方法,从回调函数获取连接状态
}
上面的代码是我们比较常见的一种逻辑,发起登录请求,登录成功后使用 token 去连接其他服务,在连接成功后,继续执行其他逻辑。如果逻辑再复杂一些,比如连接失败后根据错误码去重新登录等,我们就会陷入“回调地狱”,使得代码的可读性大大降低。
就这一情况我们可以使用 Kotlin 协程来改造我们的代码:
suspend fun login(name:String,pass:String):LoginBean = suspendCancellableCoroutine { ctn->
HttpMethods.webService.login(name,pass){
override fun onSuccess(bean: LoginBean) {
ctn.resume(bean) //协程恢复,返回结果
}
override fun onError(e: Exception) {
ctn.resumeWithException(e) //协程恢复,抛出异常
}
}
connect
函数的改造与 login
函数的改造思路相同,即:在获取到我们需要的数据的位置使用 continuation.resume
函数,在需要抛出异常的位置使用 continuation.resumeWithException
函数。
那么我们如何使用这两个改造完成的函数呢?代码示例如下:
launch{
val bean = login(name,pass) //在获取结果之前,协程是挂起状态不会执行下一步
val result = connect(bean.token)
}
相比一开始的代码,此时的代码是不是看着逻辑更为清晰更为优雅。
改造普通函数变成挂起函数,最常用的两个函数是 suspendCoroutine
与 suspendCancellableCoroutine
,这两者几乎大同小异,都是接受一个lambda表达式作为参数。区别是,前者传入的Continuation<T>
,后者传入的是CancellableContinuation<T>
。后者可以执行continuation.invokeOnCancellation { }
函数,该函数会在协程被取消时执行。可用于一些如资源需要关闭、或者网络请求等场景,实现在协程被取消时,关闭资源or取消网络请求。
扩展阅读+温故知新:
上面我们提到了 suspend
关键字,用于修饰挂起函数,挂起函数会将当前协程挂起(可以理解类似线程阻塞),直至continuation.resume
函数执行后才能继续执行后续代码。
这无疑会导致运行速度降低,如果我们后面的部分代码不需要挂起函数的返回值,需要实现类似并行的效果,直到我们需要挂起函数结果时才挂起当前的协程该如何操作呢?
答案是 async
函数:
launch{
//被async函数包含,不会挂起当前协程,相当于在其他协程执行代码
val bean = async{ login(name,pass) }
...... //其他逻辑
//执行至此需要上一步挂起函数的返回值时,执行await()函数
val result = connect(bean.await().token)
}
async
函数的返回值是 Deferred,该接口下的await
函数是一个挂起函数。执行至此时会挂起协程,直至 async
函数内的挂起函数执行完毕获得返回值。