You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ViewModel协程调用最佳实践:取消旧协程仅保留最新请求的实现方案

处理这类「仅保留最新请求、自动取消旧请求」的场景,Kotlin 协程生态下的公认最佳实践是使用 flatMapLatest(或 transformLatest)操作符配合触发器流实现,相比你提到的两种方案更简洁安全,也更符合流式编程的设计范式。

现有两种方案的缺陷

  • 手动管理Job的方案:将协程生命周期控制逻辑放在UI层,不符合职责分离原则,页面重建、多入口触发请求时很容易出现漏取消、重复管理的问题,维护成本高。
  • SharedFlow 方案:并没有真正取消旧请求,只是旧请求的结果不会最终影响UI,后台仍然会运行废弃的网络、数据库操作,属于无效的资源浪费,没有从根源解决协程泄漏问题。

最佳实践实现代码

class ScanViewModel(
  private val productRepository: ProductRepository,
  private val cartRepository: CartRepository
) : BaseViewModel<ScanUiState>(Initial) {

  // 搜索触发器流,存储当前需要查询的EAN码,null代表无查询请求
  private val _searchEanFlow = MutableStateFlow<String?>(null)

  init {
    viewModelScope.launch {
      _searchEanFlow
        // 过滤无效的空请求
        .filterNotNull()
        // 核心:新EAN到达时自动取消上一个未完成的请求协程
        .flatMapLatest { ean ->
          setState(Loading)
          // 原有商品+购物车的组合逻辑保持不变
          productRepository.getProductByEan(ean)
            .combine(cartRepository.getCart(), combineToGridItem())
        }
        .collect { result ->
          when (result) {
            is Either.Left -> {
              sendEvent(Error(R.string.error_barcode_product_not_found, null))
              setState(Initial)
            }
            is Either.Right -> {
              setState(ProductUpdated(result.right))
            }
          }
        }
    }
  }

  fun fetchProduct(ean: String) {
    // UI层仅需要更新触发器流的值即可,无需管理协程
    _searchEanFlow.value = ean
  }
}

方案优势

  1. 自动取消旧请求:flatMapLatest 内置了新值到达时取消旧协程的逻辑,旧的网络、数据库操作会被直接中断,从根源避免后台协程泄漏和无效运行。
  2. 职责完全分离:所有逻辑都封装在ViewModel内部,UI层不需要关心协程管理,也不会受页面生命周期变化的影响。
  3. 功能拓展性强:如果需要添加扫描防抖,只需要在 filterNotNull 后追加 .debounce(300) 即可实现指定时间内只处理最后一次请求,无需修改其他逻辑。
  4. 原有逻辑复用率高:商品和购物车的组合逻辑、结果处理逻辑和你原来的代码完全一致,不需要额外改造。

内容的提问来源于stack exchange,提问作者nilsi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 01:36:06