在 Kotlin callbackFlow 构建器中捕获异常

Catch an Exception inside Kotlin callbackFlow builder

我有一个看起来像这样的消费者:

// ItemConsumer.kt
try {
    job = itemService
            .connect()
            .flowOn(Dispatchers.IO)
            .catch { e ->
                throw e
            }
            .onEach {
                // Update UI for each item collected
            }
            .launchIn(viewModelScope)
} catch (e : Exception) {
    // Handle exception
}

我有一个看起来像这样的制作人:

// ItemService.kt (Producer)
fun connect():Flow<Item> = callbackFlow {
    check(1 == 0) // This is expected to throw an IllegalStateException
}

我了解 .catch { } 将处理消费者的 .collect { } 或 .onEach { } 块中出现的任何问题。但是我怎样才能抛出 callbackFlow { } 构建器内部发生的异常,以便它可以被消费者的 .catch { } 捕获?

你不能。流构建器函数永远不会抛出异常。 Flows 是冷的,这意味着在调用 collect 之前不会执行任何代码。即使收集的执行出现异常,您也始终可以成功构建流。

catch 运算符 catches only upstream exceptions (that is an exception from all the operators above catch, but not below it)。在您的代码中,onEachcollect(通过 launchIn)中的任何异常都不会被捕获。您需要将此运算符移至 onEach 下方以捕获 所有 异常。

此运算符 catches exceptions in the flow completion. It could be possible to catch them in callbackFlow builder only if the flow completes normally or exceptionally. As said it will never be thrown by the flow builder. But since this builder uses a Channel under the hood, in order to complete the flow exceptionally, you have to close 手动 non-null 原因:

abstract fun close(cause: Throwable? = null): Boolean

请记住,这可能会违反透明度:“流必须 对异常透明 ,并且在 flow { ... } 构建器中发出值违反了异常透明度来自 try/catch 块的内部”。我可能错了,但我认为,因为你使用 catch,这“保留了这个异常透明度并允许封装它的异常处理”。

所以消费者可能是这样的:

job = itemService.connect()
    .flowOn(Dispatchers.IO)
    .onEach {
        // update UI
    }
    .catch { e ->
        // handle exception
    }
    .launchIn(viewModelScope)

并且生产者应该像这样关闭异常:

fun connect(): Flow<Item> = callbackFlow {
    try {
        check(1 == 0) // throws an exception
    } catch (e: Exception) {
        close(e) // close manually with exception
    }
}