专栏首页Bennyhuo写一个 golang 风格的协程扩展

写一个 golang 风格的协程扩展

本文概要

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

launch {
    val deferred =   ...
    val (result, error) = deferred.awaitOrError()
    if(error == null){
        dealWithResult(result)    
    } else {
        handleError(error)
    }
}

需求的诞生

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

bytes, err := ioutil.ReadFile("Hello.go")
if err == nil {
    fmt.Print(string(bytes))
} else {
    fmt.Print(err)
}

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

try {
    val deferred =   ...
    val result = deferred.await()
    dealWithResult(result) 
} catch (e: Exception) {
    handleError(error)
}

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

返回多个值

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

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

suspend fun <T> Deferred<T>.awaitOrError(): Pair<T, Throwable> {
    return try {
        await() to null
    } catch (e: Exception) {
        null to e
    }
}

可空类型的返回值

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

suspend fun <T> Deferred<T>.awaitOrError2(): Pair<T?, Throwable?> {
    ...
}

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

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

val (result, err) = async { ... }.awaitOrError()

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

if(err != null) {
    if (result != null) {
        dealWithResult(result)
    }
}

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

平台类型

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

T!

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

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

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 类当中添加两个方法:

...
    public T component1() {
        return result;
    }

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

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

suspend fun <T> Deferred<T>.awaitOrError(): Result<T> {
    return try {
        Result.of(await())
    } catch (e: Exception) {
        Result.of(e)
    }
}

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

launch {
    val deferred =   ...
    val (result, error) = deferred.awaitOrError()
    if(error == null){
        dealWithResult(result)    
    } else {
        handleError(error)
    }
}

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

小结

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


本文分享自微信公众号 - Kotlin(KotlinX),作者:bennyhuo

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-03-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • PagerAdapter 正确地移除 Item

    很久没有写 UI 相关的程序,感觉都生疏了。最近用 FragmentPagerAdapter,配合 TabLayout,感觉还不错。不过很快就遇到了一个问题,我...

    bennyhuo
  • Case Study:读取设备的温度值?

    如果让你去读取 Android 设备的温度,并且告诉你这些温度的值都存在 /sys/class/thermal/thermal_zone 开头的目录下的 tem...

    bennyhuo
  • 正确地使用 Kotlin 的 internal

    Kotlin 的 internal 是一个比较有用的访问控制关键字,特别是当你开发一些 SDK 给别人用时,有些类的 API 只能为 public 却又不想让外...

    bennyhuo
  • python学习(13)

    #coding=utf-8 result = [] for i in range(1,6): result.append(chr(97+i-1)+str(i))...

    py3study
  • python列表与元组的用法

    7.列表生成式   #[i*i for i in range(10)]       [i*i for i in range(10) if i>5]

    py3study
  • 算法提高 9-2 文本加密

    问题描述   先编写函数EncryptChar,按照下述规则将给定的字符c转化(加密)为新的字符:”A”转化”B”,”B”转化为”C”,… …”Z”转化为”...

    AI那点小事
  • Leetcode: Reverse Bits

    题目: Reverse bits of a given 32 bits unsigned integer.

    卡尔曼和玻尔兹曼谁曼
  • 2015.11.30 HTML5真题练习

    HTML5学堂:每天一道题,强壮程序员!今日主要涉及昨日题目的解答,以及一道涉及函数的形参实参、arguments对象的题目 HTML5真题【2015.11.2...

    HTML5学堂
  • Laravel框架实现抢红包功能示例

    可以在信息界面自行选择 抢红包 或者 发红包 1.发红包,跳转到相应的发红包界面

    砸漏
  • 数值的整数次方

扫码关注云+社区

领取腾讯云代金券