带有 catch 运算符的 Kotlin Flow 仍能完成

Gre*_*reg 9 kotlin kotlin-coroutines kotlin-flow

我无法理解catch运算符在 kotlin 中的工作原理Flow。 这是捕获文档

问题:

  1. 为什么 a 的存在不允许catch在Flow遇到异常时继续,而不是完成?
  2. 操作员的放置catch似乎改变了行为。为什么我不能将catch运算符放在链的末尾以看到相同的结果?在我的示例中,只有当我将其放置在 BEFORE 之前,它才会执行onEach。

示例要点

第一个例子,放置catchBEFORE onEach:

fun main() {
    // Flow of lambdas that return a String (or throw an Exception)
    flowOf<() -> String>({ "Hello " }, { error("error") }, { "World" })
        // Map to the result of the invocation of the lambda
        .map { it() }
        // This line will emit the error String, but then the flow completes anyway.
        // I would expect the flow to continue onto "World"
        .catch { emit("[Exception caught] ") }
        .onEach { println(it) }
        .launchIn(GlobalScope)
}
Run Code Online (Sandbox Code Playgroud)

实际结果: Hello [Exception caught]

预期结果: Hello [Exception caught] World

第二个例子,放置catchAFTER onEach:

fun main() {
    // Flow of lambdas that return a String (or throw an Exception)
    flowOf<() -> String>({ "Hello " }, { error("error") }, { "World" })
        // Map to the result of the invocation of the lambda
        .map { it() }
        .onEach { println(it) }
        // I would expect this catch to emit, but it never gets here.
        .catch { emit("[Exception caught] ") }
        .launchIn(GlobalScope)
}
Run Code Online (Sandbox Code Playgroud)

实际结果: Hello

预期结果: Hello [Exception caught] World

或者,由于onEach发生在 的发射之前catch,因此catch的发射将被忽略?在这种情况下,预期的输出将是这样的?: Hello World

Ale*_*exT 15

对我来说,解释你正在做什么的最简单方法是将其简化为同步代码。你基本上是这样做的:

fun main() {
    val list = listOf("Hello", "error", "World")
    
    try {
        for (s in list) {
            if (s == "error") error("this is the error message here")
            println(s)
        }
    } catch (e: Exception) {
        println("the exception message is: ${e.localizedMessage}")
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

Hello
the exception message is: this is the error message here
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,异常被捕获了,但是它不能阻止循环的停止for。与异常停止函数的方式相同map。

Flow.catch 将捕获异常并阻止其传播(除非您再次抛出它),但它无法后退一步(回到地图乐趣)并告诉它神奇地从下一个元素所在的位置重新开始或等等

如果你想要这样,你需要在乐趣try/catch中加入一个正常的东西.map。所以它会是:

fun main() {
    val list = listOf("Hello", "error", "World")


    for (s in list) {
        try {
            if (s == "error") error("this is the error message here")
            println(s)
        } catch (e: Exception) {
            println("the exception message is: ${e.localizedMessage}")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

Hello
the exception message is: this is the error message here
World
Run Code Online (Sandbox Code Playgroud)

通常使用的方法Flow.catch是捕获会阻止下一步的异常,例如如果您有:

//pseudo

flow
    .map{/*code*/}
    .filterNotNull()
    .doSomethingRisky() //this can throw an exception
    .catch {} //do something about it
    .doSomethingElse()
Run Code Online (Sandbox Code Playgroud)

在这种情况下,即使doSomethingRisky抛出异常,流程仍然会到达doSomethingElse. 这或多或少都是利用了Flow.catch.