Kotlin Coroutines Flow 系列(三) 异常处理

attractive-beautiful-beauty-face-607891.jpg

五. Flow 异常处理

Flow 可使用传统的 try...catch 来捕获异常:java

fun main() = runBlocking {
    flow {
        emit(1)
        try {
            throw RuntimeException()
        } catch (e: Exception) {
            e.stackTrace
        }

    }.onCompletion { println("Done") }
        .collect { println(it) }
}
复制代码

另外,也可使用 catch 操做符来捕获异常。bash

5.1 catch 操做符

上一篇文章Flow VS RxJava2曾讲述过 onCompletion 操做符。网络

可是 onCompletion 不能捕获异常,只能用于判断是否有异常。post

fun main() = runBlocking {
    flow {
        emit(1)
        throw RuntimeException()
    }.onCompletion { cause ->
        if (cause != null)
            println("Flow completed exceptionally")
        else
            println("Done")
    }.collect { println(it) }
}
复制代码

执行结果:动画

1
Flow completed exceptionally
Exception in thread "main" java.lang.RuntimeException
......
复制代码

catch 操做符能够捕获来自上游的异常ui

fun main() = runBlocking {
    flow {
        emit(1)
        throw RuntimeException()
    }
    .onCompletion { cause ->
        if (cause != null)
            println("Flow completed exceptionally")
        else
            println("Done")
    }
    .catch{ println("catch exception") }
    .collect { println(it) }
}
复制代码

执行结果:spa

1
Flow completed exceptionally
catch exception
复制代码

上面的代码若是把 onCompletion、catch 交换一下位置,则 catch 操做符捕获到异常后,不会影响到下游。所以,onCompletion 操做符再也不打印"Flow completed exceptionally"日志

fun main() = runBlocking {
    flow {
        emit(1)
        throw RuntimeException()
    }
    .catch{ println("catch exception") }
    .onCompletion { cause ->
        if (cause != null)
            println("Flow completed exceptionally")
        else
            println("Done")
    }
    .collect { println(it) }
}
复制代码

执行结果:code

1
catch exception
Done
复制代码

catch 操做符用于实现异常透明化处理。例如在 catch 操做符内,可使用 throw 再次抛出异常、可使用 emit() 转换为发射值、能够用于打印或者其余业务逻辑的处理等等。cdn

可是,catch 只是中间操做符不能捕获下游的异常,相似 collect 内的异常。

对于下游的异常,能够屡次使用 catch 操做符来解决。

对于 collect 内的异常,除了传统的 try...catch 以外,还能够借助 onEach 操做符。把业务逻辑放到 onEach 操做符内,在 onEach 以后是 catch 操做符,最后是 collect()。

fun main() = runBlocking<Unit> {
    flow {
         ......
    }
    .onEach {
          ......
    }
   .catch { ... }
   .collect()
}
复制代码

5.2 retry、retryWhen 操做符

像 RxJava 同样,Flow 也有重试的操做符。

若是上游遇到了异常,并使用了 retry 操做符,则 retry 会让 Flow 最多重试 retries 指定的次数。

public fun <T> Flow<T>.retry( retries: Long = Long.MAX_VALUE, predicate: suspend (cause: Throwable) -> Boolean = { true }
): Flow<T> {
    require(retries > 0) { "Expected positive amount of retries, but had $retries" }
    return retryWhen { cause, attempt -> attempt < retries && predicate(cause) }
}
复制代码

例如,下面打印了三次"Emitting 1"、"Emitting 2",最后两次是经过 retry 操做符打印出来的。

fun main() = runBlocking {

    (1..5).asFlow().onEach {
        if (it == 3) throw RuntimeException("Error on $it")
    }.retry(2) {

        if (it is RuntimeException) {
            return@retry true
        }
        false
    }
    .onEach { println("Emitting $it") }
    .catch { it.printStackTrace() }
    .collect()
}
复制代码

执行结果:

Emitting 1
Emitting 2
Emitting 1
Emitting 2
Emitting 1
Emitting 2
java.lang.RuntimeException: Error on 3
......
复制代码

retry 操做符最终调用的是 retryWhen 操做符。下面的代码跟刚才的执行结果一致:

fun main() = runBlocking {

    (1..5).asFlow().onEach {
        if (it == 3) throw RuntimeException("Error on $it")
    }
    .onEach { println("Emitting $it") }
    .retryWhen { cause, attempt ->
        attempt < 2
    }
    .catch { it.printStackTrace() }
    .collect()
}
复制代码

由于 retryWhen 操做符的参数是谓词,当谓词返回 true 时才会进行重试。谓词还接收一个 attempt 做为参数表示尝试的次数,该次数是从0开始的。

六. Flow Lifecycle

RxJava 的 do 操做符可以监听 Observables 的生命周期的各个阶段。

Flow 并无多那么丰富的操做符来监听其生命周期的各个阶段,目前只有 onStart、onCompletion 来监听 Flow 的建立和结束。

fun main() = runBlocking {

    (1..5).asFlow().onEach {
        if (it == 3) throw RuntimeException("Error on $it")
    }
    .onStart { println("Starting flow") }
    .onEach { println("On each $it") }
    .catch { println("Exception : ${it.message}") }
    .onCompletion { println("Flow completed") }
    .collect()
}
复制代码

执行结果:

Starting flow
On each 1
On each 2
Flow completed
Exception : Error on 3
复制代码

例举他们的使用场景: 好比,在 Android 开发中使用 Flow 建立网络请求时,经过 onStart 操做符调用 loading 动画以及网络请求结束后经过 onCompletion 操做符取消动画。

再好比,在借助这些操做符作一些日志的打印。

fun <T> Flow<T>.log(opName: String) = onStart {
    println("Loading $opName")
}.onEach {
    println("Loaded $opName : $it")
}.onCompletion { maybeErr ->
    maybeErr?.let {
        println("Error $opName: $it")
    } ?: println("Completed $opName")
}
复制代码

该系列的相关文章:

Kotlin Coroutines Flow 系列(一) Flow 基本使用

Kotlin Coroutines Flow 系列(二) Flow VS RxJava2

相关文章
相关标签/搜索