Как обрабатывать исключения список отложенных в Kotlin, чтобы получить успешные элементы?

#kotlin #kotlin-coroutines

Вопрос:

У меня есть что-то вроде

 listOf(
    getItem1Async(),
    getItem2Async(), //error
    getItem3Async()
).awaitAl
           
 

любая из функций может получить исключение, но поскольку они возвращают отложенное, исключение не обрабатывается до вызова функции await (). Я хочу, например, если getItem2Async завершится неудачно, чтобы создание моего списка не завершилось неудачно, но чтобы получить значение по умолчанию или нулевое значение и сохранить успешные вызовы.

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

Комментарии:

1. Вам нужно будет поместить их в их собственную область сопрограммы с помощью пользовательского обработчика ошибок.

Ответ №1:

Во-первых, нам нужно использовать supervisorScope(), чтобы разбитые дочерние сопрограммы не влияли на другие сопрограммы. Затем мы можем использовать функции Deferred.getCompleted(), Deferred.getCompletionExceptionOrNull() или аналогичные функции для получения результата:

 suspend fun main() {
    supervisorScope {
        listOf(
            async { "hello" },
            async { check(false) },
            async { "world" },
        )
    }.map { it.getCompletedOrNull() }
        .forEach(::println)
}

@OptIn(ExperimentalCoroutinesApi::class)
fun <T> Deferred<T>.getCompletedOrNull() = if (isCancelled) null else getCompleted()
 

Комментарии:

1. getCompleted() переосмысливает любое исключение, отличное от исключения CancellationException, поэтому, возможно, его следует завернуть: runCatching { getCompleted() }.getOrNull() .

2. Хм… не isCancelled == false означает ли это, что сопрограмма удалась? Название isCancelled немного вводит в заблуждение, потому что сбои в сопрограммах считаются отменами. Кроме того, если бы это было так , то я считаю, что приведенный выше код должен быть брошен map() , но на самом деле он возвращает значение null для второго элемента.

3. Вы правы насчет такого поведения. Я ошибочно полагал, что «отменено» и «завершено» являются взаимоисключающими противоположностями, и что отмена предназначена исключительно для исключений отмены.

4. Честно говоря, API Job / Deferred кажется мне неестественным. Здесь нет ничего простого isSucceeded , но вместо этого нам нужно проверить isCompleted и isCancelled . Нет getCompletedOrNull() , но есть getCompletionExceptionOrNull() . Это сбивает с толку. @Tenfour04