我很难理解catch
算子在kotlinFlow
中的工作方式。下面是catch文档
问题:
- 为什么
catch
的存在不允许Flow
在遇到异常时继续,而不是完成? catch
操作符的位置似乎改变了行为。为什么我不能把catch
操作符在链的末尾看到相同的结果?在我的例子中,只有当我把它放在onEach
之前,它才会执行。
例子要点
第一个例子,将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
函数的方式相同。
流。Catch将捕获一个异常并阻止它的传播(除非你再次抛出它),但它不能后退一步(到map fun),并告诉它从下一个元素的位置神奇地重新开始,等等。
如果你想这样做,你需要在.map
fun里面放一个正常的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
的用法。
虽然AlexT响应是完全正确的,但有一个技巧可以保持流的活动。您可以将实际的流包装到另一个流中。内部/实际流将完成,但外部流将保持活跃。如果你的流程失败,但你想提供"重试",这很方便。按钮:
private val sourceFlow = MutableStateFlow(0)
private val innerFlow = flow { ... } // Whatever your flow is!
val outerFlow = sourceFlow.flatMapLatest {
innerFlow.catch {
// Handling, e.g. emitting an error state to the UI (can show retry button)
emit(ViewState.Error("Some error")
}.onStart {
println("Inner start")
}.onCompletion {
println("Inner end")
}
}.onStart {
println("Outer start")
}.onCompletion {
println("Outer end")
}
如果您想重试整个流程,只需执行sourceFlow.update { it + 1 }
。这将更新MutableStateFlow
中的0
,导致再次调用flatMapLatest
,然后再次启动innerFlow
。您可以使用MutableSharedFlow
而不是MutableStateFlow<Int>
执行相同的操作,但我发现这有点棘手,因为在这种情况下,您需要从协程发出。
使用此设置,您需要订阅outerFlow
,它将发出innerFlow
的所有值