catch и onCompletion: обработка ошибок в Flow
Перехватывайте исключения выше по потоку и реагируйте на события завершения потока.
«catch и onCompletion: обработка ошибок в Flow» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.
Основы обработки исключений в потоке
Исключения в потоке завершают его. Без обработки исключение передаётся сборщику, и поток заканчивается.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
try {
flow {
emit(1)
throw RuntimeException("stream error")
emit(2)
}.collect { println(it) }
} catch (e: Exception) {
println("Caught: ${e.message}")
}
}Оператор обработки исключений
catch обрабатывает исключения из исходного потока и может выдавать резервные значения или повторно выбрасывать исключение. Он не перехватывает исключения, выброшенные самим сборщиком.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
flow {
emit(1)
throw RuntimeException("oops")
}.catch { e ->
println("Caught: ${e.message}")
emit(-1) // fallback value
}.collect { println(it) } // 1, then -1
}Обработка только исключений из исходного потока
catch перехватывает только исключения из построителя потока и вышестоящих операторов, но не из лямбда-выражения collect.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
try {
flowOf(1, 2, 3)
.catch { e -> println("upstream: ${e.message}") }
.collect {
if (it == 2) throw RuntimeException("collector error") // not caught by catch
println(it)
}
} catch (e: Exception) {
println("Collector exception: ${e.message}")
}
}Повторный выброс в обработчике
Можно проверить исключение и выборочно повторно выбросить те исключения, которые невозможно обработать.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
try {
flow {
emit(1)
throw IllegalStateException("state error")
}.catch { e ->
if (e is RuntimeException) { emit(-1); return@catch }
throw e // rethrow unknown exceptions
}.collect { println(it) }
} catch (e: Exception) {
println("Rethrown: ${e.message}")
}
}Оператор действий при завершении
onCompletion выполняется при завершении потока — штатном или вызванном исключением. Его параметр cause имеет ненулевое значение при ошибке.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
flowOf(1, 2, 3)
.onCompletion { cause ->
if (cause == null) println("Completed normally")
else println("Failed: ${cause.message}")
}
.collect { println(it) }
}Действия при завершении после ошибки
onCompletion срабатывает даже при возникновении исключения в вышестоящем потоке. Он получает исключение в параметре cause, но не подавляет его — исключение всё равно распространяется дальше.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
try {
flow {
emit(1)
throw RuntimeException("error")
}.onCompletion { cause ->
println("onCompletion cause=${cause?.message}")
}.collect { println(it) }
} catch (e: Exception) {
println("outer catch: ${e.message}")
}
}Сочетание обработки ошибок и действий при завершении
Используйте catch для восстановления и выдачи резервных значений, а затем onCompletion для записи в журнал или освобождения ресурсов независимо от результата.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
flow {
emit("ok")
throw RuntimeException("network error")
}.catch { e ->
emit("fallback") // recover
}.onCompletion {
println("Stream done — release resources")
}.collect { println(it) }
}Оператор повторных попыток
retry(n) { ... } заново подписывается на исходный поток при исключении не более n раз.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
var attempt = 0
fun unstableFlow() = flow {
attempt++
if (attempt < 3) throw RuntimeException("attempt $attempt failed")
emit("success on attempt $attempt")
}
fun main() = runBlocking {
unstableFlow()
.retry(3) { e -> println("retrying: ${e.message}"); true }
.collect { println(it) }
}Настройка повторных попыток
retryWhen предоставляет исключение и число попыток, поэтому можно добавить задержку между попытками или отфильтровать исключения, при которых следует повторить операцию.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
var n = 0
flow {
if (n++ < 2) throw IOException("IO error")
emit("done")
}.retryWhen { cause, attempt ->
cause is IOException && attempt < 3
}.collect { println(it) }
}Оператор запуска перед началом
onStart выполняется перед первой выдачей значения — это удобно для отображения состояния загрузки или инициализации ресурсов до поступления данных.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
flowOf("data")
.onStart { emit("Loading...") }
.onCompletion { emit("Done") }
.collect { println(it) }
}Обработка ошибок при реальных вызовах интерфейса
Оборачивайте потоки репозитория в catch, чтобы преобразовывать исключения в ошибки предметной области, а в onCompletion — чтобы скрывать индикаторы загрузки.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
sealed class State { object Loading : State(); data class Data(val v: String) : State(); data class Error(val msg: String) : State() }
fun apiFlow(): Flow<String> = flow { delay(50); emit("response") }
fun uiFlow(): Flow<State> = apiFlow()
.map { State.Data(it) as State }
.onStart { emit(State.Loading) }
.catch { e -> emit(State.Error(e.message ?: "unknown")) }
fun main() = runBlocking { uiFlow().collect { println(it) } }Быстрая проверка
Каково основное ограничение оператора catch?
Итоги
Перехват исключений обрабатывает исключения из исходного потока и может выдавать резервные значения. Обработка завершения выполняется всегда — при обычном завершении и при ошибке — для очистки ресурсов или журналирования. Объединяйте их с повтором/retryWhen для создания устойчивых конвейеров потоков.
Изучай Kotlin с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 51
- Уроки
- 203
Часто задаваемые вопросы
Урок «catch и onCompletion: обработка ошибок в Flow» бесплатный?
Да — полный текст урока «catch и onCompletion: обработка ошибок в Flow» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.
Чему я научусь в уроке «catch и onCompletion: обработка ошибок в Flow»?
Перехватывайте исключения выше по потоку и реагируйте на события завершения потока. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Kotlin Academy?
Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «catch и onCompletion: обработка ошибок в Flow»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Kotlin Academy?
Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Операторы Flow: map, filter, transform и take
- catch и onCompletion: обработка ошибок в Flow
- combine и zip: объединение нескольких Flow
- flowOn и buffer: контекст и противодавление