Kotlin Academy · Урок

catch и onCompletion: обработка ошибок в Flow

Перехватывайте исключения выше по потоку и реагируйте на события завершения потока.

Урок 2 из 413 шагов

«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 — локальная установка не требуется.

Все уроки этого курса

  1. Операторы Flow: map, filter, transform и take
  2. catch и onCompletion: обработка ошибок в Flow
  3. combine и zip: объединение нескольких Flow
  4. flowOn и buffer: контекст и противодавление
← Назад к Kotlin Academy