Распространение исключений в async/await
Разберитесь, как распространяются исключения из async и когда следует использовать try-await.
«Распространение исключений в async/await» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.
Асинхронные вычисления сохраняют исключения
В отличие от launch, async сохраняет исключение в возвращаемом объекте Deferred. Оно повторно выбрасывается только при вызове await().
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred = async {
throw RuntimeException("async error")
}
try {
deferred.await() // exception rethrown here
} catch (e: RuntimeException) {
println("Caught: ${e.message}")
}
}Исключение без await()
Если никогда не вызывать await(), исключение будет молча отброшено при наличии обычного родителя Job. Предпочитайте supervisorScope и всегда вызывайте await.
import kotlinx.coroutines.*
fun main() = runBlocking {
// Exception stored in deferred, never retrieved:
val d = async { throw RuntimeException("lost exception") }
delay(100) // d has failed — exception never surfaced
println("d.isCancelled: ${d.isCancelled}")
}Асинхронные вычисления в coroutineScope
В обычном coroutineScope, если дочерняя корутина async выбрасывает исключение, которое распространяется дальше и не перехватывается, область видимости и все соседние корутины отменяются.
import kotlinx.coroutines.*
fun main() = runBlocking {
try {
coroutineScope {
val a = async { "A" }
val b = async { throw RuntimeException("B failed") }
println(a.await())
println(b.await()) // propagates, cancels scope
}
} catch (e: RuntimeException) {
println("Scope failed: ${e.message}")
}
}Асинхронные вычисления в supervisorScope
В supervisorScope исключения из async НЕ распространяются на соседние корутины. Каждый await()` должен обрабатываться отдельно.
import kotlinx.coroutines.*
fun main() = runBlocking {
supervisorScope {
val a = async { "A" }
val b = async { throw RuntimeException("B failed") }
println(a.await())
try { println(b.await()) }
catch (e: RuntimeException) { println("b failed: ${e.message}") }
}
}awaitAll для нескольких асинхронных вычислений
awaitAll(d1, d2, d3) ожидает завершения всех отложенных вычислений. Если хотя бы одно завершается с ошибкой, функция немедленно выбрасывает исключение и отменяет остальные.
import kotlinx.coroutines.*
fun main() = runBlocking {
try {
val results = awaitAll(
async { "result1" },
async { throw RuntimeException("task 2 failed") },
async { "result3" }
)
println(results)
} catch (e: Exception) {
println("awaitAll failed: ${e.message}")
}
}runCatching с асинхронными вычислениями
Оберните каждый вызов await() в runCatching, чтобы собрать результаты и ошибки, не прерывая выполнение при первой ошибке.
import kotlinx.coroutines.*
fun main() = runBlocking {
supervisorScope {
val deferreds = listOf(
async { "A" },
async { throw RuntimeException("B") },
async { "C" }
)
val results = deferreds.map { runCatching { it.await() } }
results.forEach { println(it) }
}
}Параллельные задачи с накоплением ошибок
Собирайте все сбои параллельных асинхронных задач и сообщайте о них вместе, вместо того чтобы немедленно завершать выполнение при первой ошибке.
import kotlinx.coroutines.*
fun main() = runBlocking {
val tasks = listOf("a", "b", "c")
val results = supervisorScope {
tasks.map { t ->
async {
if (t == "b") throw RuntimeException("b failed")
t.uppercase()
}
}.map { runCatching { it.await() } }
}
val errors = results.filter { it.isFailure }
val successes = results.mapNotNull { it.getOrNull() }
println("Success: $successes, Errors: ${errors.size}")
}Сохранение типа исключения
Исключения, выброшенные внутри async, сохраняют свой тип. Перехватывайте конкретный тип исключения в месте вызова await() для точной обработки ошибок.
import kotlinx.coroutines.*
class NetworkException(msg: String) : RuntimeException(msg)
fun main() = runBlocking {
val d = async { throw NetworkException("timeout") }
try {
d.await()
} catch (e: NetworkException) {
println("Network error: ${e.message}")
} catch (e: Exception) {
println("Other error: ${e.message}")
}
}Deferred.getCompleted() для проверки без приостановки
После join() используйте getCompleted(), чтобы синхронно получить результат. Метод выбрасывает исключение, если отложенное вычисление завершилось с ошибкой.
import kotlinx.coroutines.*
fun main() = runBlocking {
val d = async { 42 }
d.join() // wait without caring about result
try {
val result = d.getCompleted() // synchronous — no suspend
println("Result: $result")
} catch (e: Exception) {
println("Failed: ${e.message}")
}
}Структурированная конкурентность и асинхронные вычисления
При структурированной конкурентности отложенное вычисление async всегда связано с родительской областью видимости. При отмене области видимости отложенное вычисление также отменяется — бесхозных задач не остаётся.
import kotlinx.coroutines.*
fun main() = runBlocking {
val scope = CoroutineScope(SupervisorJob())
val d = scope.async {
delay(1000)
"result"
}
scope.cancel() // cancels d too
try { d.await() }
catch (e: CancellationException) { println("Cancelled as expected") }
}Лучший подход: всегда вызывайте await
Всегда вызывайте await() для каждого отложенного вычисления async, даже если результат вам не нужен. Это делает исключения явными и предотвращает незаметные сбои.
import kotlinx.coroutines.*
fun main() = runBlocking {
supervisorScope {
val d = async {
// do background work
"done"
}
// Always await:
val result = runCatching { d.await() }
println(result)
}
}Быстрая проверка
Когда становится видимым исключение из блока async { throw ... }?
Повторение
async сохраняет исключения в Deferred; они проявляются при вызове await(). В coroutineScope распространение исключения отменяет соседние корутины. В supervisorScope каждый вызов await должен обрабатывать собственную ошибку. Всегда ожидайте завершения каждого отложенного вычисления.
Изучай Kotlin с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 51
- Уроки
- 203
Часто задаваемые вопросы
Урок «Распространение исключений в async/await» бесплатный?
Да — полный текст урока «Распространение исключений в async/await» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.
Чему я научусь в уроке «Распространение исключений в async/await»?
Разберитесь, как распространяются исключения из async и когда следует использовать try-await. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Kotlin Academy?
Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Распространение исключений в async/await»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Kotlin Academy?
Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- SupervisorJob и Job: изоляция сбоев
- CoroutineExceptionHandler: глобальный обработчик неперехваченных исключений
- Распространение исключений в async/await
- Проектирование устойчивых архитектур сопрограмм