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

Android:使用Kotlin协程等待回调结果——从RxJava迁移时mapByPage调用挂起函数报错的解决方案咨询

解决在mapByPage中调用挂起函数的问题

首先,咱们先理清你代码里的两个核心问题:

  • viewModelScope.launch { ... }.await() 是错误用法:launch返回的是Job对象,它没有await方法——只有async返回的Deferred才有这个方法。
  • 根本矛盾:mapByPage的lambda参数不是挂起函数,所以你不能直接在里面调用挂起函数(比如fetchProducts)或者await。

下面是几种可行的解决方案,按推荐程度排序:

方案1:补全挂起函数错误处理 + 使用库的协程扩展(最优)

首先先把你fetchProducts里的错误处理补全,不然异常会被吞掉,上层完全感知不到加载失败:

suspend fun fetchProducts(ids: List<String>) = suspendCoroutine<List<ProductItem>> { continuation ->
    val getProducts = repo.search(ids, null, null)
    getProducts.subscribe(
        { callResult ->
            productRequestInProgress.postValue(false)
            continuation.resume(callResult.result())
        },
        { error ->
            productRequestInProgress.postValue(false)
            continuation.resumeWithException(error) // 必须补上这一行!
        }
    )
}

如果你使用的搜索库(比如Algolia)提供了协程支持,建议直接用库的挂起版API替换手动的suspendCoroutine,代码会更简洁可靠:

suspend fun fetchProducts(ids: List<String>): List<ProductItem> {
    return try {
        val callResult = repo.search(ids, null, null).await() // 用库提供的挂起版search
        productRequestInProgress.postValue(false)
        callResult.result()
    } catch (error: Exception) {
        productRequestInProgress.postValue(false)
        throw error // 抛出让上层处理异常
    }
}

接下来检查库是否提供了支持挂起lambda的mapByPage版本(比如命名为mapByPageSuspend),如果有的话直接使用:

val dataSourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit ->
    Product(hit.json.getValue("id").jsonPrimitive.content)
}.mapByPageSuspend { page -> // 注意是挂起版的mapByPage
    fetchProducts(page.mapNotNull { it.id })
}

方案2:临时用runBlocking(谨慎使用)

如果你的mapByPage是在后台线程执行的,可以用runBlocking把挂起操作转换成同步调用,但要严格控制线程:

val dataSourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit ->
    Product(hit.json.getValue("id").jsonPrimitive.content)
}.mapByPage { page ->
    // 指定IO线程,绝对不能在UI线程用runBlocking,会导致ANR
    runBlocking(Dispatchers.IO) { 
        fetchProducts(page.mapNotNull { it.id }) 
    }
}

⚠️ 注意:如果mapByPage在UI线程执行,这个方法会阻塞UI导致应用无响应,所以一定要确保代码块跑在后台线程。

方案3:自定义DataSource(最灵活,适配分页场景)

如果上面的方法都不适用,你可以自定义一个DataSource,在数据加载逻辑里直接用协程调用fetchProducts,完全绕开mapByPage的限制:

class ProductsDataSource(
    private val searcher: SearcherSingleIndex,
    private val viewModelScope: CoroutineScope
) : PageKeyedDataSource<Int, ProductItem>() {

    override fun loadInitial(
        params: LoadInitialParams<Int>,
        callback: LoadInitialCallback<Int, ProductItem>
    ) {
        viewModelScope.launch {
            try {
                // 1. 从搜索器获取初始分页数据(根据你的实际搜索逻辑调整)
                val initialHits = searcher.search(...).result()
                val productIds = initialHits.mapNotNull { it.id }
                // 2. 调用挂起函数获取产品详情
                val products = fetchProducts(productIds)
                // 3. 把结果回调给分页框架
                callback.onResult(products, null, 1)
            } catch (e: Exception) {
                // 处理加载失败,比如回调错误或触发重试
                e.printStackTrace()
            } finally {
                productRequestInProgress.postValue(false)
            }
        }
    }

    override fun loadAfter(
        params: LoadParams<Int>,
        callback: LoadCallback<Int, ProductItem>
    ) {
        viewModelScope.launch {
            try {
                // 加载下一页的搜索结果
                val nextHits = searcher.search(...).result()
                val productIds = nextHits.mapNotNull { it.id }
                val products = fetchProducts(productIds)
                callback.onResult(products, params.key + 1)
            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                productRequestInProgress.postValue(false)
            }
        }
    }

    override fun loadBefore(
        params: LoadParams<Int>,
        callback: LoadCallback<Int, ProductItem>
    ) {
        // 不需要加载上一页的话,空实现即可
    }
}

然后创建对应的DataSourceFactory:

val dataSourceFactory = object : DataSource.Factory<Int, ProductItem>() {
    override fun create(): DataSource<Int, ProductItem> {
        return ProductsDataSource(searcher, viewModelScope)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 07:23:11