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
相关产品推荐
相关产品推荐

