带有 catch 运算符的 Kotlin Flow 仍然可以完成

Kotlin Flow with catch operator still completes

我无法理解 catch 运算符在 kotlin Flow 中的工作原理。 Here is the catch documentation

问题:

  1. 为什么 catch 的存在不允许 Flow 在遇到异常时继续,而不是完成?
  2. catch 运算符的放置似乎改变了行为。为什么我不能将 catch 运算符放在链的末尾来查看相同的结果?在我的示例中,它仅在我将它放在 onEach.
  3. 之前执行

Example gist

第一个示例,将 catch 放在 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)
}

实际结果: Hello [Exception caught]

预期结果: Hello [Exception caught] World

第二个示例,将 catch 放在 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)
}

实际结果: Hello

预期结果: Hello [Exception caught] World

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

我解释你在那里做什么的最简单方法是将它简化为同步代码。你基本上是这样做的:

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}")
    }
}

输出:

Hello
the exception message is: this is the error message here

如你所见,异常被捕获,但它不能阻止for循环的停止。该异常停止 map 函数的方式相同。

Flow.catch 会捕获一个异常并阻止它传播(除非你再次抛出它),但它不能后退一步(到地图的乐趣)并告诉它神奇地从下一个元素应该在哪里等等

如果你想要那个,你需要在 .map 中放一个普通的 try/catch 乐趣。所以它会是:

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}")
        }
    }
}

输出:

Hello
the exception message is: this is the error message here
World

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

//pseudo

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

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