Kotlin(二十)异步流-操作符<4> 异常处理
2021-08-04 本文已影响0人
zcwfeng
-
流异常
当运算符中的发射器或代码抛出异常时,流收集可以带有异常的完成。 有几种处理异常的方法。
收集器 try 与 catch
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i) // 发射下一个值
}
}
fun main() = runBlocking<Unit> {
try {
simple().collect { value ->
println(value)
check(value <= 1) { "Collected $value" }
}
} catch (e: Throwable) {
println("Caught $e")
}
}
Emitting 1
1
Emitting 2
2
Caught java.lang.IllegalStateException: Collected 2
这段代码成功的在末端操作符 [collect]中捕获了异常,并且, 如我们所见,在这之后不再发出任何值
一切都已捕获
fun simple(): Flow<String> =
flow {
for (i in 1..3) {
println("Emitting $i")
emit(i) // 发射下一个值
}
}
.map { value ->
check(value <= 1) { "Crashed on $value" }
"string $value"
}
fun main() = runBlocking<Unit> {
try {
simple().collect { value -> println(value) }
} catch (e: Throwable) {
println("Caught $e")
}
}
Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2
仍然会捕获该异常并停止收集:
前面的示例实际上捕获了在发射器或任何过渡或末端操作符中发生的任何异常。 例如,让我们修改代码以便将发出的值[映射]为字符串, 但是相应的代码会产生一个异常
异常透明性
但是,发射器的代码如何封装其异常处理行为?
流必须对异常透明,即在 flow { ... }
构建器内部的 try/catch
块中[发射]值是违反异常透明性的。这样可以保证收集器抛出的一个异常能被像先前示例中那样的 try/catch
块捕获。
发射器可以使用 [catch](操作符来保留此异常的透明性并允许封装它的异常处理。catch 操作符的代码块可以分析异常并根据捕获到的异常以不同的方式对其做出反应
- 可以使用
throw
重新抛出异常。 - 可以使用 [catch] 代码块中的 [emit]( 将异常转换为值发射出去。
- 可以将异常忽略,或用日志打印,或使用一些其他代码处理它。
simple()
.catch { e -> emit("Caught $e") } // 发射一个异常
.collect { value -> println(value) }
Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2
透明捕获
过渡操作符遵循异常透明性,仅捕获上游异常(catch 操作符上游的异常,但是它下面的不是)。 如果 collect { ... } 块(位于 catch 之下)抛出一个异常,那么异常会逃逸:
fun simple(): Flow<Int> = flow {
for (i in 1..3) {
println("Emitting $i")
emit(i)
}
}
fun main() = runBlocking<Unit> {
simple()
.catch { e -> println("Caught $e") } // 不会捕获下游异常
.collect { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
}
Emitting 1
1
Emitting 2
Exception in thread "main" java.lang.IllegalStateException: Collected 2
at FileKt$main$1$invokeSuspend$$inlined$collect$1.emit (Collect.kt:135)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt$catchImpl$$inlined$collect$1.emit (Collect.kt:136)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke (SafeCollector.kt:15)
尽管有 catch 操作符,但不会打印“Caught …”消息:
声明式捕获
我们可以将 [catch] 操作符的声明性与处理所有异常的期望相结合,将 [collect]操作符的代码块移动到 [onEach]中,并将其放到 catch
操作符之前。收集该流必须由调用无参的 collect()
来触发:
simple()
.onEach { value ->
check(value <= 1) { "Collected $value" }
println(value)
}
.catch { e -> println("Caught $e") }
.collect()
Emitting 1
1
Emitting 2
Caught java.lang.IllegalStateException: Collected 2
现在我们可以看到已经打印了“Caught …”消息,并且我们可以在没有显式使用 try/catch 块的情况下捕获所有异常