Kotlin Flow异常处理

359 阅读1分钟

一、基础异常捕获机制

  1. ‌**try/catch 代码块**‌
    适用于同步代码和 Flow 收集阶段的异常捕获。

    flow { emit(1); throw RuntimeException() }
        .collect { value ->
            try { process(value) }
            catch (e: Exception) { 
                println("收集异常: $e") 
            }
        }
    
    • 作用‌:捕获 collect 块内的处理逻辑异常1。
    • 局限‌:无法捕获 flow 构建器或中间操作符中的异常16。

二、Flow 流专用异常操作符

1. catch:统一处理上游异常
  • 捕获范围‌:flow 构建器和 mapfilter 等中间操作符抛出的异常26。

  • 示例‌:

    flow {
        emit(1)
        throw IOException("网络错误")
    }
    .catch { e -> 
        emit(-1)  // 异常后发射兜底数据
        println("发射异常: $e")
    }
    .collect { println(it) }
    

    输出‌:

    textCopy Code
    1 → -1
    
2. onCompletion:流结束回调
  • 功能‌:无论流正常结束还是异常终止,均会触发。

    flow { emit(1) }
        .onCompletion { cause ->
            if (cause != null) println("流异常终止: $cause")
            else println("流正常结束")
        }
        .collect { ... }
    
    • 注意‌:cause 参数为 null 表示正常结束26。
3. retryWhen:条件式重试
  • 场景‌:网络请求失败后按策略重试。

    flow { fetchData() }
        .retryWhen { cause, attempt ->
            (cause is IOException) && (attempt < 3)
        }
        .collect { ... }
    
    • 逻辑‌:当异常为 IOException 且重试次数小于 3 时自动重试2。

三、操作符对比与选型

操作符捕获阶段典型场景引用
try/catch收集阶段 (collect)消费端业务逻辑异常处理16
catch生产阶段 (emit)统一处理数据生成异常12
retryWhen全流程网络请求失败后有限次重试2
onCompletion全流程资源清理或状态记录26

四、最佳实践

  1. 分层处理异常

    • 生产端‌:使用 catch 恢复默认值或兜底数据12。
    • 消费端‌:结合 try/catch 处理业务逻辑异常1。
  2. 资源释放
    onCompletionfinally 块中释放资源(如关闭文件句柄):

    flow { readFile() }
        .onCompletion { closeFile() }
        .collect { ... }
    
  3. 调试与监控
    通过 onCompletion 记录流执行状态和异常信息26。


五、完整示例

案例一、

fun fetchDataFlow(): Flow<Data> = flow {
    val data = api.fetch()  // 可能抛出 IOException
    emit(data)
}.catch { e ->
    emit(Data.EMPTY)  // 异常时返回空数据
    logError(e)       // 记录错误日志
}.retryWhen { cause, attempt ->
    (cause is IOException) && (attempt < 2)  // 最多重试 2 次
}.onCompletion { cause ->
    if (cause == null) println("数据流正常结束")
}

案例二

val  getNumbers = flowOf(1,2,3,4,5).onEach { delay(2000) }

fun main() = runBlocking {
    try {
        getNumbers.collect{
            println("$it")
            if (it==3) throw KotlinNullPointerException("下游计算过程中出现问题")
        }
    }catch (e:Exception){
        println("e:$e")
    }

    //对上游出现异常采用声明式
    flow{
        listOf(1,2,3,4,5,6,7,8).forEach{values -> emit(values)
            delay(2000)
            if(values == 5) throw KotlinNullPointerException("上游计算过程发出异常信息")
        }
    }.catch {
        println("异常信息:e $it")
        emit(-1)
    }.onEach {
        delay(2000)
    }.collect{
        println(it)
    }


    //对上游出现异常采用声明式
    flow{
        listOf(100).forEach{values -> emit(values)
            delay(2000)
           throw KotlinNullPointerException("上游计算过程发出异常信息")
        }
    }.catch {
        emit(200)
    }.onEach {
        delay(2000)
    }.collect{
        println(it)
    }

}

图片.png


通过合理组合 catchretryWhen 等操作符,可实现健壮的异步数据处理流程,兼顾异常恢复与资源管理