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

问题描述

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

问题:

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

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 " },{ "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 会捕获一个异常并阻止它传播(除非你再次抛出它),但它不能退后一步(到地图乐趣)并告诉它神奇地从下一个元素的位置重新开始本来是或等

如果需要,您需要在 try/catch 乐趣中放入一个普通的 .map。所以应该是:

fun main() {
    val list = listOf("Hello","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 的用法。