Flow 可使用傳統的 try...catch 來捕獲異常:java
fun main() = runBlocking {
flow {
emit(1)
try {
throw RuntimeException()
} catch (e: Exception) {
e.stackTrace
}
}.onCompletion { println("Done") }
.collect { println(it) }
}
複製代碼
另外,也可使用 catch 操做符來捕獲異常。bash
上一篇文章Flow VS RxJava2曾講述過 onCompletion 操做符。網絡
可是 onCompletion 不能捕獲異常,只能用於判斷是否有異常。post
fun main() = runBlocking {
flow {
emit(1)
throw RuntimeException()
}.onCompletion { cause ->
if (cause != null)
println("Flow completed exceptionally")
else
println("Done")
}.collect { println(it) }
}
複製代碼
執行結果:動畫
1
Flow completed exceptionally
Exception in thread "main" java.lang.RuntimeException
......
複製代碼
catch 操做符能夠捕獲來自上游的異常ui
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) }
}
複製代碼
執行結果:spa
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) }
}
複製代碼
執行結果:code
1
catch exception
Done
複製代碼
catch 操做符用於實現異常透明化處理。例如在 catch 操做符內,可使用 throw 再次拋出異常、可使用 emit() 轉換爲發射值、能夠用於打印或者其餘業務邏輯的處理等等。cdn
可是,catch 只是中間操做符不能捕獲下游的異常,相似 collect 內的異常。
對於下游的異常,能夠屢次使用 catch 操做符來解決。
對於 collect 內的異常,除了傳統的 try...catch 以外,還能夠藉助 onEach 操做符。把業務邏輯放到 onEach 操做符內,在 onEach 以後是 catch 操做符,最後是 collect()。
fun main() = runBlocking<Unit> {
flow {
......
}
.onEach {
......
}
.catch { ... }
.collect()
}
複製代碼
像 RxJava 同樣,Flow 也有重試的操做符。
若是上游遇到了異常,並使用了 retry 操做符,則 retry 會讓 Flow 最多重試 retries 指定的次數。
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()
}
複製代碼
執行結果:
Emitting 1
Emitting 2
Emitting 1
Emitting 2
Emitting 1
Emitting 2
java.lang.RuntimeException: Error on 3
......
複製代碼
retry 操做符最終調用的是 retryWhen 操做符。下面的代碼跟剛纔的執行結果一致:
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 操做符的參數是謂詞,當謂詞返回 true 時纔會進行重試。謂詞還接收一個 attempt 做爲參數表示嘗試的次數,該次數是從0開始的。
RxJava 的 do 操做符可以監聽 Observables 的生命週期的各個階段。
Flow 並無多那麼豐富的操做符來監聽其生命週期的各個階段,目前只有 onStart、onCompletion 來監聽 Flow 的建立和結束。
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()
}
複製代碼
執行結果:
Starting flow
On each 1
On each 2
Flow completed
Exception : Error on 3
複製代碼
例舉他們的使用場景: 好比,在 Android 開發中使用 Flow 建立網絡請求時,經過 onStart 操做符調用 loading 動畫以及網絡請求結束後經過 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")
}
複製代碼
該系列的相關文章: