首页 > 解决方案 > 在 for 循环中等待所有凌空请求

问题描述

在我的函数中,我需要返回一个由带有一些 Volley 请求的 for 循环填充的列表。所以我需要等待所有这些请求在返回列表之前被终止。

我认为我需要异步 CoroutineScope 来完成这项工作,但我不知道如何等待所有这些响应。

这是我的代码:

suspend fun getListOfAbility(pokemon: Pokemon) : MutableList<Ability> {
    val listOfAbility: MutableList<Ability> = emptyList<Ability>() as MutableList<Ability>
    CoroutineScope(Dispatchers.IO).launch {
        /**
         * get the pokemon json
         */
        val pokemonJsonObjectRequest = JsonObjectRequest(
            Request.Method.GET,
            "$pokemonUrl${pokemon.id}",
            null,
            {
                /**
                 * onResponse
                 *
                 * get the list of pokemon abilities
                 */
                val abilitiesJO = it.getJSONObject("abilities")
                val abilityObjectType = object : TypeToken<List<PokemonGson.AbilityObjectGson>>() { }.type
                val abilityListGson = Gson().fromJson<List<PokemonGson.AbilityObjectGson>>(abilitiesJO.toString(), abilityObjectType)
                /**
                 * for each ability listed on pokemon info get the full Ability Object
                 */
                for((index, abilityObjectGson) in abilityListGson.withIndex()) {
                    val abilityJsonObjectRequest = JsonObjectRequest(
                        Request.Method.GET,
                        abilityObjectGson.ability.url,
                        null,
                        {
                            abilityJson ->
                            /**
                             * onResponse
                             *
                             * get the full ability info
                             */
                            val abilityType = object : TypeToken<AbilityGson>() { }.type
                            val abilityGson = Gson().fromJson<AbilityGson>(abilityJson.toString(), abilityType)

                            /**
                             * fill the Ability entry of listOfAbility with the correct language
                             */
                            val ability = Ability(abilityGson, abilityListGson[index].is_hidden)

                            listOfAbility.add(ability)

                        },
                        {
                            /**
                             * onError
                             */
                            Log.d("POKEMON", "Pokemon ability error")
                        }
                    )

                    requestQueue.add(abilityJsonObjectRequest)
                }

            },
            {
                /**
                 * onError
                 */
                Log.d("POKEMON", "Pokemon request error")
            }
        )
        requestQueue.add(pokemonJsonObjectRequest)
    }

    //wait
    return listOfAbility
}

标签: kotlinandroid-volleykotlin-coroutines

解决方案


要在挂起函数中使用基于回调的代码,您需要使用suspendCoroutineor将其转换为挂起函数suspendCancellableCoroutine。因此,在这种情况下,要替换创建 JSONObjectRequest 和侦听器、将其排队到 RequestQueue 并以某种方式等待它的操作,我将创建一个如下所示的挂起函数:

suspend inline fun RequestQueue.getJSONObjectOrNull(
    method: Int,
    url: String,
    jsonRequest: JSONObject?,
    crossinline onError: (VolleyError)->Unit = {}
): JSONObject? = suspendCancellableCoroutine { continuation ->
    val request = JsonObjectRequest(
        method,
        url,
        jsonRequest,
        { result: JSONObject -> continuation.resume(result) },
        { error ->
            onError(error)
            continuation.resume(null)
        }
    )
    add(request)
    continuation.invokeOnCancellation { request.cancel() }
}

它直接返回 JSONObject 结果,如果失败则返回 null。如果您想记录错误,您可以选择对错误运行回调。

然后,您可以使用它来编写函数的更顺序版本,而不是基于回调的版本。您可以使用 的模式coroutineScope { async { list.map { ... } } }.awaitAll()将列表的每个项目转换为使用并行协程的其他项目。

这是您的功能的未经测试的版本。我让它在失败时返回一个空列表。您也可以在失败时返回 null,这可能更有用,因此调用函数可以决定在失败时执行不同的操作。

private fun VolleyError.logDebug() {
    Log.d("POKEMON", "Pokemon request error: $this")
}

suspend fun getListOfAbility(pokemon: Pokemon): List<Ability> {
    val pokemonJsonObject = requestQueue.getJSONObjectOrNull(Request.Method.GET, "$pokemonUrl${pokemon.id}", null, VolleyError::logDebug)

    pokemonJsonObject ?: return emptyList()

    val abilitiesJO = pokemonJsonObject.getJSONObject("abilities")
    val abilityObjectType = object : TypeToken<List<PokemonGson.AbilityObjectGson>>() {}.type
    val abilityListGson: List<Wrapper> = Gson().fromJson<List<PokemonGson.AbilityObjectGson>>(
        abilitiesJO.toString(),
        abilityObjectType
    )

    return coroutineScope {
        abilityListGson.map {
            async {
                requestQueue.getJSONObjectOrNull(Request.Method.GET, it.ability.url, null, VolleyError::logDebug)
            }
        }
    }
        .awaitAll()
        .filterNotNull()
        .map { abilityJson ->
            val abilityType = object : TypeToken<AbilityGson>() {}.type
            val abilityGson = Gson().fromJson<AbilityGson>(abilityJson.toString(), abilityType)
            Ability(abilityGson, abilityListGson[index].is_hidden)
        }
}

推荐阅读