五. Flow 異常處理
Flow 可以使用傳統(tǒng)的 try...catch 來捕獲異常:
fun main() = runBlocking {
flow {
emit(1)
try {
throw RuntimeException()
} catch (e: Exception) {
e.stackTrace
}
}.onCompletion { println("Done") }
.collect { println(it) }
}
另外茵臭,也可以使用 catch 操作符來捕獲異常汹胃。
5.1 catch 操作符
上一篇文章Flow VS RxJava2曾講述過 onCompletion 操作符曲伊。
但是 onCompletion 不能捕獲異常谆棱,只能用于判斷是否有異常寡喝。
fun main() = runBlocking {
flow {
emit(1)
throw RuntimeException()
}.onCompletion { cause ->
if (cause != null)
println("Flow completed exceptionally")
else
println("Done")
}.collect { println(it) }
}
執(zhí)行結(jié)果:
1
Flow completed exceptionally
Exception in thread "main" java.lang.RuntimeException
......
catch 操作符可以捕獲來自上游的異常
fun main() = runBlocking {
flow {
emit(1)
throw RuntimeException()
}
.onCompletion { cause ->
if (cause != null)
println("Flow completed exceptionally")
else
println("Done")
}
.catch{ println("catch exception") }
.collect { println(it) }
}
執(zhí)行結(jié)果:
1
Flow completed exceptionally
catch exception
上面的代碼如果把 onCompletion、catch 交換一下位置绷杜,則 catch 操作符捕獲到異常后直秆,不會影響到下游。因此鞭盟,onCompletion 操作符不再打印"Flow completed exceptionally"
fun main() = runBlocking {
flow {
emit(1)
throw RuntimeException()
}
.catch{ println("catch exception") }
.onCompletion { cause ->
if (cause != null)
println("Flow completed exceptionally")
else
println("Done")
}
.collect { println(it) }
}
執(zhí)行結(jié)果:
1
catch exception
Done
catch 操作符用于實現(xiàn)異常透明化處理圾结。例如在 catch 操作符內(nèi),可以使用 throw 再次拋出異常齿诉、可以使用 emit() 轉(zhuǎn)換為發(fā)射值筝野、可以用于打印或者其他業(yè)務邏輯的處理等等。
但是粤剧,catch 只是中間操作符不能捕獲下游的異常歇竟,類似 collect 內(nèi)的異常。
對于下游的異常抵恋,可以多次使用 catch 操作符來解決焕议。
對于 collect 內(nèi)的異常,除了傳統(tǒng)的 try...catch 之外弧关,還可以借助 onEach 操作符盅安。把業(yè)務邏輯放到 onEach 操作符內(nèi),在 onEach 之后是 catch 操作符世囊,最后是 collect()别瞭。
fun main() = runBlocking<Unit> {
flow {
......
}
.onEach {
......
}
.catch { ... }
.collect()
}
5.2 retry、retryWhen 操作符
像 RxJava 一樣株憾,F(xiàn)low 也有重試的操作符蝙寨。
如果上游遇到了異常,并使用了 retry 操作符,則 retry 會讓 Flow 最多重試 retries 指定的次數(shù)籽慢。
public fun <T> Flow<T>.retry(
retries: Long = Long.MAX_VALUE,
predicate: suspend (cause: Throwable) -> Boolean = { true }
): Flow<T> {
require(retries > 0) { "Expected positive amount of retries, but had $retries" }
return retryWhen { cause, attempt -> attempt < retries && predicate(cause) }
}
例如,下面打印了三次"Emitting 1"猫胁、"Emitting 2"箱亿,最后兩次是通過 retry 操作符打印出來的。
fun main() = runBlocking {
(1..5).asFlow().onEach {
if (it == 3) throw RuntimeException("Error on $it")
}.retry(2) {
if (it is RuntimeException) {
return@retry true
}
false
}
.onEach { println("Emitting $it") }
.catch { it.printStackTrace() }
.collect()
}
執(zhí)行結(jié)果:
Emitting 1
Emitting 2
Emitting 1
Emitting 2
Emitting 1
Emitting 2
java.lang.RuntimeException: Error on 3
......
retry 操作符最終調(diào)用的是 retryWhen 操作符弃秆。下面的代碼跟剛才的執(zhí)行結(jié)果一致:
fun main() = runBlocking {
(1..5).asFlow().onEach {
if (it == 3) throw RuntimeException("Error on $it")
}
.onEach { println("Emitting $it") }
.retryWhen { cause, attempt ->
attempt < 2
}
.catch { it.printStackTrace() }
.collect()
}
因為 retryWhen 操作符的參數(shù)是謂詞届惋,當謂詞返回 true 時才會進行重試。謂詞還接收一個 attempt 作為參數(shù)表示嘗試的次數(shù)菠赚,該次數(shù)是從0開始的脑豹。
六. Flow Lifecycle
RxJava 的 do 操作符能夠監(jiān)聽 Observables 的生命周期的各個階段。
Flow 并沒有多那么豐富的操作符來監(jiān)聽其生命周期的各個階段衡查,目前只有 onStart瘩欺、onCompletion 來監(jiān)聽 Flow 的創(chuàng)建和結(jié)束。
fun main() = runBlocking {
(1..5).asFlow().onEach {
if (it == 3) throw RuntimeException("Error on $it")
}
.onStart { println("Starting flow") }
.onEach { println("On each $it") }
.catch { println("Exception : ${it.message}") }
.onCompletion { println("Flow completed") }
.collect()
}
執(zhí)行結(jié)果:
Starting flow
On each 1
On each 2
Flow completed
Exception : Error on 3
例舉他們的使用場景:
比如拌牲,在 Android 開發(fā)中使用 Flow 創(chuàng)建網(wǎng)絡(luò)請求時俱饿,通過 onStart 操作符調(diào)用 loading 動畫以及網(wǎng)絡(luò)請求結(jié)束后通過 onCompletion 操作符取消動畫。
再比如塌忽,在借助這些操作符做一些日志的打印拍埠。
fun <T> Flow<T>.log(opName: String) = onStart {
println("Loading $opName")
}.onEach {
println("Loaded $opName : $it")
}.onCompletion { maybeErr ->
maybeErr?.let {
println("Error $opName: $it")
} ?: println("Completed $opName")
}
該系列的相關(guān)文章:
Kotlin Coroutines Flow 系列(一) Flow 基本使用
Kotlin Coroutines Flow 系列(二) Flow VS RxJava2
Kotlin Coroutines Flow 系列(四) 線程操作
Kotlin Coroutines Flow 系列(五) 其他的操作符