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

Clean Architecture架构下ViewModel中如何并行调用多个API请求

解决方案

问题核心原因

你之前用withContext(Dispatchers.IO)包裹函数没生效,是因为Flow是冷流,线程调度由上游发射逻辑决定,且你如果在同一协程内顺序调用函数、没有为每个请求启动独立协程的话,就会变成串行执行。另外你原有代码中每个请求都单独修改isLoading状态,会出现前一个请求结束就关闭加载状态、后一个请求还在执行的异常表现。

实现方案(两种可选)

方案1:用combine操作符合并Flow(更推荐)

适合需要统一管控所有请求的加载、错误状态的场景,所有请求并行执行,全部返回后统一更新状态:

class MyJobsViewModel constructor(
    private val myJobsUseCases: MyJobsUseCases,
    private val clientNavigator: ClientNavigator
) : ViewModel(), ClientNavigator by clientNavigator {

    private val _state = mutableStateOf(MyJobsState())
    val state: State<MyJobsState> get() = _state

    // 统一触发所有并行请求
    fun fetchAllJobData() {
        viewModelScope.launch {
            // 1. 定义所有请求的Flow
            val allJobsFlow = myJobsUseCases.getJobsUseCase.invoke(offset = 0, limit = 10, type = JobTypeEnum.ALL)
            val activeJobsFlow = myJobsUseCases.getJobsUseCase.invoke(offset = 0, limit = 10, type = JobTypeEnum.ACTIVE)
            // 你剩下的3个请求的Flow同理定义
            
            // 2. combine合并所有Flow,并行执行
            combine(
                allJobsFlow,
                activeJobsFlow
                // 剩下的3个Flow依次加入
            ) { allJobsRes, activeJobsRes /* 剩下的请求结果依次定义 */ ->
                // 统一处理加载状态:只要有一个请求还在加载,全局就是加载中
                val isLoading = allJobsRes is Resource.Loading || activeJobsRes is Resource.Loading
                // 统一处理错误:任意请求报错就返回错误
                val error = listOfNotNull(
                    (allJobsRes as? Resource.Error)?.message,
                    (activeJobsRes as? Resource.Error)?.message
                ).firstOrNull()
                // 组装数据
                val allJobItems = (allJobsRes as? Resource.Success)?.data ?: JobItemsResponse()
                val activeJobItems = (activeJobsRes as? Resource.Success)?.data ?: JobItemsResponse()

                MyJobsState(
                    isLoading = isLoading,
                    error = error,
                    allJobItems = allJobItems,
                    activeJobItems = activeJobItems
                )
            }.collect { newState ->
                _state.value = newState
            }
        }
    }
}

方案2:用async启动独立协程并行执行

适合每个请求需要单独处理结果、不需要统一管控状态的场景:

fun fetchAllJobData() {
    viewModelScope.launch {
        // 每个请求用async启动,自动并行执行
        val allJobsDeferred = async { 
            myJobsUseCases.getJobsUseCase.invoke(offset = 0, limit = 10, type = JobTypeEnum.ALL).first() 
        }
        val activeJobsDeferred = async { 
            myJobsUseCases.getJobsUseCase.invoke(offset = 0, limit = 10, type = JobTypeEnum.ACTIVE).first() 
        }
        // 剩下3个请求同理定义async

        // 等待所有请求返回后更新状态
        val allJobsRes = allJobsDeferred.await()
        val activeJobsRes = activeJobsDeferred.await()

        // 处理结果更新状态
        _state.value = state.value.copy(
            allJobItems = (allJobsRes as? Resource.Success)?.data ?: JobItemsResponse(),
            activeJobItems = (activeJobsRes as? Resource.Success)?.data ?: JobItemsResponse(),
            isLoading = false
        )
    }
}

注意事项

  • 不需要在ViewModel中手动切Dispatchers.IO,API请求的线程调度应该在UseCase或者Repository层的Flow上游指定,符合Clean架构的职责划分
  • 如果你的UseCase返回的Flow是单次发射的(普通API请求都是单次返回结果),可以用first()操作符直接获取结果,不需要持续收集

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 19:06:06