Дождитесь всех запросов на залп в цикле for

#kotlin #android-volley #kotlin-coroutines

Вопрос:

В моей функции мне нужно вернуть список, заполненный циклом for с некоторым запросом на залп. Поэтому мне нужно подождать, пока все эти запросы будут завершены, прежде чем возвращать список.

Я думаю, что мне нужна асинхронная сопрограмма для выполнения этой работы, но я не знаю, как я могу дождаться всего этого ответа.

Это мой код:

 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
}
 

Ответ №1:

Чтобы использовать код на основе обратного вызова в функции приостановки, вам необходимо преобразовать его в функцию приостановки с помощью suspendCoroutine или suspendCancellableCoroutine . Поэтому в этом случае, чтобы заменить действие создания JsonObjectRequest и прослушивателя, постановки его в очередь запроса и ожидания его каким-то образом, я бы создал функцию приостановки, подобную этой:

 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)
        }
}