前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >写一个 golang 风格的协程扩展

写一个 golang 风格的协程扩展

作者头像
bennyhuo
发布2020-02-20 13:21:02
5120
发布2020-02-20 13:21:02
举报
文章被收录于专栏:Bennyhuo

本文概要

Kotlin 的协程库 kotlinx.coroutines 当中有个比较常用的 async 函数,返回的 Deferred<T> 有个 await 方法,这个方法在子协程正常返回时返回结果,否则直接抛异常,而我们的目标是定义一个扩展 awaitOrError

代码语言:javascript
复制
launch {
    val deferred =   ...
    val (result, error) = deferred.awaitOrError()
    if(error == null){
        dealWithResult(result)    
    } else {
        handleError(error)
    }
}

需求的诞生

最近因为要定制 BatteryHistorian 这个框架的某些小功能,近距离接触了一些 golang,发现这门语言当中很多可能出异常的函数调用返回两个结果,例如:

代码语言:javascript
复制
bytes, err := ioutil.ReadFile("Hello.go")
if err == nil {
    fmt.Print(string(bytes))
} else {
    fmt.Print(err)
}

而对比我们 Kotlin 的协程库, await 要么返回结果,要么抛异常,我对于这一点觉得还是有点儿不太喜欢的,尽管我们可以用一个 try...catch 来捕获异步任务的异常,但写起来还是感觉在犯错误。

代码语言:javascript
复制
try {
    val deferred =   ...
    val result = deferred.await()
    dealWithResult(result) 
} catch (e: Exception) {
    handleError(error)
}

我当时想,如果 Kotlin 的协程能写出 golang 风格的返回,那体验起来还是很不错的。

返回多个值

可是刚要动手写,就要扑街了,Kotlin 不支持多个返回值哎,咋整?

没关系,别忘了我们还有 Pair<A,B>,我们只需要在扩展的方法中返回这个类型,调用处用数据类的解构写法,返回多个值也不是什么问题了:

代码语言:javascript
复制
suspend fun <T> Deferred<T>.awaitOrError(): Pair<T, Throwable> {
    return try {
        await() to null
    } catch (e: Exception) {
        null to e
    }
}

可空类型的返回值

嗯,看上去不错,只是没法通过编译。为什么呢?返回结果的泛型参数需要定义为可空类型才可以。

代码语言:javascript
复制
suspend fun <T> Deferred<T>.awaitOrError2(): Pair<T?, Throwable?> {
    ...
}

这也是没办法的事儿啊,我们总是有返回 null 的可能嘛。

嗯,这回不仅看上去不错,编译也能通过了。不过,用起来却有点儿蛋疼。

代码语言:javascript
复制
val (result, err) = async { ... }.awaitOrError()

这里拿到的 result 也好, err 也好,都是可空类型的,显然这对于后者来说到不是什么问题,而对于 result 来说,可空类型意味着我们在后面使用它的时候就需要判空:

代码语言:javascript
复制
if(err != null) {
    if (result != null) {
        dealWithResult(result)
    }
}

额,这就有点儿尴尬了,因为从我们的代码的角度,只要 err 不为空,那么 result 一定不为空,可是编译器却对于这样的一对儿互斥关系一无所知。

平台类型

所以我们进入了一个尴尬的境地,我们想要的 Kotlin 语法本身似乎无法直接给我们了。我们现在就是想要让 awaitOrError 返回的 result 类型为不可空类型,或者至少看起来像是这样,这样我们用起来会轻松一些;而一旦它真正会是 null 的时候,我们又不会去使用它,这样做本身没有什么风险。只是,有什么途径允许我们这么做呢?

代码语言:javascript
复制
T!

平台类型。没错就是平台类型。如果返回的 resultT!,那么 Kotlin 就不会对它有太多的约束,你愿意把它当做可空类型,那他就可以是可空类型,反之,你愿意把它当做不可空类型,只要在使用前能确定它不为空就好。听起来不错。

所以我们决定返回值不用 Pair,而是使用一个 Java 类:

代码语言:javascript
复制
public class Result<T> {

    private T result;
    private Throwable error;

    public T getResult() {
        return result;
    }

    @Nullable
    public Throwable getError() {
        return error;
    }

    public static <T> Result<T> of(Throwable error) {
        Result<T> result = new Result<T>();
        result.error = error;
        return result;
    }

    public static <T> Result<T> of(T result) {
        Result<T> resultJava = new Result<T>();
        resultJava.result = result;
        return resultJava;
    }
}

注意到对于 getError ,我明确用注解标注其返回值为可空,这就是告诉 Kotlin,这个可以为 null,而 getResult 没有。

Java 数据类与解构

只是,这时候又产生了新的问题,Java 中要怎么定义数据类呢?不是数据类又怎么解构呢?

相比之下,这个问题就简单多了,如果你对 Kotlin 的数据类的字节码比较熟悉,你就会想到只要我们在前面的 Result 类当中添加两个方法:

代码语言:javascript
复制
...
    public T component1() {
        return result;
    }

    @Nullable
    public Throwable component2() {
        return error;
    }
    ...

只要你定义了 componentN 方法,哪怕是在 Java 当中定义,Kotlin 当中对于这个类的实例也是可以进行解构的。有了前面的方法,我们的 awaitOrError 就可以进一步修改了:

代码语言:javascript
复制
suspend fun <T> Deferred<T>.awaitOrError(): Result<T> {
    return try {
        Result.of(await())
    } catch (e: Exception) {
        Result.of(e)
    }
}

而在调用处,也能按照我们的意愿去检查错误,使用结果,就像文章开头提到的那样:

代码语言:javascript
复制
launch {
    val deferred =   ...
    val (result, error) = deferred.awaitOrError()
    if(error == null){
        dealWithResult(result)    
    } else {
        handleError(error)
    }
}

注意到上述的 resultT! 类型,即平台类型。

小结

终于可以在协程中抛弃 try...catch... 了!


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-03-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kotlin 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本文概要
  • 需求的诞生
  • 返回多个值
  • 可空类型的返回值
  • 平台类型
  • Java 数据类与解构
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档