如何抑制协程流程中的错误,使流程无法完成?

Arc*_*nes 4 kotlin kotlin-coroutines kotlin-flow

我有一个可能会抛出错误的流程,如下所示:

val myFlow = flow {
    emit("1")
    delay(2000)
    emit("2")
    delay(2000)
    emit("3")
    delay(2000)
    emit("4")
    delay(2000)
    throw Exception() // here it would throw an error
    delay(10000)
    emit("6")  // because the flow completes on error, it doesn't emit this
}
Run Code Online (Sandbox Code Playgroud)

我的问题是,当抛出错误时,即使我添加.catch { error -> emit("5") }.. 它仍然完成流程,因此"6"不会发出。

myFlow.catch { error ->
    emit("5")
}.onEach {
    println("$it")
}.onCompletion {
    println("Complete")
}.launchIn(scope)
Run Code Online (Sandbox Code Playgroud)

结果是:

1
2
3
4
5
Complete
Run Code Online (Sandbox Code Playgroud)

我需要它是:

1
2
3
4
5
6
Complete
Run Code Online (Sandbox Code Playgroud)

我想吞下错误而不是使流程完整。我怎样才能实现这个目标?

And*_*rew 5

好吧,我知道这不是完全相同的例子,但我怀疑我的情况有些相似。

假设您有某种有风险的流程。流程可能会引发异常。但是,即使在异常之后,您也希望保持与流的连接,因为它会发出一些重要的信息,例如服务器实时更新。

您的目标是抑制异常或将其转换为某些数据,并无论如何继续监听实时更新。

    var counter = 0

    val riskyFlow = flow {

        while (true) {
            counter++

            delay(1000)

            if (counter == 3) 
                throw IllegalStateException("Oops! Error.")
            else 
                emit("Server update")
            
        }
    }
Run Code Online (Sandbox Code Playgroud)

如果您使用catch,则有风险的流程将在您错误地发出您想要的内容后完成。

riskyFlow
        .catch { cause -> emit("Emit on error") }
        .onEach { println(it) }
        .launchIn(GlobalScope)
Run Code Online (Sandbox Code Playgroud)

解决方案

使用retryretryWhen. 这样,您可以通过发出一些有关异常的数据来抑制异常,然后您将立即重新启动与流的连接,因此它将继续发出其数据。

riskyFlow
        .retryWhen { cause, attempt ->
            emit("emit on error")
            true
        }
        .onEach { println(it) }
        .launchIn(GlobalScope)
Run Code Online (Sandbox Code Playgroud)

输出是:

I/System.out: Server update
I/System.out: Server update
I/System.out: emit on error
I/System.out: Server update
I/System.out: Server update
Run Code Online (Sandbox Code Playgroud)