← Назад к вопросам

Как обрабатываются исключение в Flow?

2.0 Middle🔥 181 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обработка исключений в Flow

Это критический вопрос про надёжность асинхронного кода. Flow имеет встроенные механизмы для обработки ошибок, и важно знать все варианты.

1. catch оператор — базовая обработка

flow<User> {
    val user = api.getUser()  // Может выбросить исключение
    emit(user)
}
.catch { exception ->
    // Ловим исключение
    println("Error: ${exception.message}")
    // emit() значение по умолчанию
    emit(User.EMPTY)
}
.collect { user ->
    updateUI(user)
}

Важно: catch ловит только исключения в upstream (до catch). Если исключение в collect, оно не будет поймано.

2. onCompletion — финальный блок

flow<Int> {
    emit(1)
    emit(2)
    throw Exception("Error")
}
.onCompletion { exception ->
    if (exception != null) {
        println("Flow завершился с ошибкой: ${exception.message}")
    } else {
        println("Flow завершился успешно")
    }
}
.collect { value ->
    println(value)
}

Разница от catch: onCompletion вызывается ВСЕГДА (успех или ошибка), а catch только при ошибке.

3. retryWhen — повторная попытка с условием

flow<User> {
    val user = api.getUser()
    emit(user)
}
.retryWhen { exception, attempt ->
    // Повторяем если это сетевая ошибка и попыток меньше 3
    exception is IOException && attempt < 3
}
.catch { exception ->
    // Если все повторы не помогли
    println("Failed after 3 attempts: ${exception.message}")
    emit(User.EMPTY)
}
.collect { user ->
    updateUI(user)
}

4. timeout — обработка зависаний

flow<Data> {
    val data = api.fetchData()  // Может зависнуть
    emit(data)
}
.timeout(5000)  // Выбросит TimeoutException если 5+ секунд
.catch { exception ->
    if (exception is TimeoutException) {
        println("Запрос зависнул")
    }
    emit(Data.EMPTY)
}
.collect { data ->
    updateUI(data)
}

5. Обработка в collect блоке

flow<User> {
    emit(api.getUser())
}
.collect(
    onEach = { user ->
        updateUI(user)
    },
    onCompletion = { exception ->
        if (exception != null) {
            showError(exception.message)
        } else {
            showSuccess("Data loaded")
        }
    }
)

Альтернатива (современный подход):

try {
    flow<User> {
        emit(api.getUser())
    }.collect { user ->
        updateUI(user)
    }
} catch (e: Exception) {
    showError(e.message)
}

6. Трансформация ошибок

flow<User> {
    emit(api.getUser())
}
.catch { exception ->
    // Преобразуем исключение
    val message = when (exception) {
        is IOException -> "Network error"
        is JsonException -> "Invalid data"
        else -> "Unknown error"
    }
    emit(User.empty(message))
}
.collect { user ->
    updateUI(user)
}

7. Многоуровневая обработка

class UserRepository {
    fun getUser(): Flow<User> = flow {
        val user = api.getUser()  // Может выброситься
        emit(user)
    }
    .catch { exception ->
        // Логируем на уровне repository
        logger.error("Repository error", exception)
        // Пробрасываем выше для обработки в UI
        throw exception
    }
}

class UserViewModel : ViewModel() {
    val userState: StateFlow<Result<User>> = repository.getUser()
        .map { Result.success(it) as Result<User> }
        .catch { exception ->
            // Ловим на уровне ViewModel и преобразуем в Result
            emit(Result.failure(exception))
        }
        .stateIn(viewModelScope, SharingStarted.Eagerly, Result.loading())
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.userState.collect { result ->
                    when (result) {
                        is Result.Success -> updateUI(result.data)
                        is Result.Failure -> showError(result.exception)
                        is Result.Loading -> showLoading()
                    }
                }
            }
        }
    }
}

8. Обработка исключений при emit()

flow<Int> {
    try {
        emit(api.getData())
    } catch (e: Exception) {
        println("Error during emit: ${e.message}")
        // Не пробрасываем — продолжаем Flow
    }
}
.catch { exception ->
    // Это будут только исключения из других блоков
    println("Downstream error: ${exception.message}")
}
.collect { value ->
    updateUI(value)
}

9. Отмена Flow при ошибке

// По умолчанию при ошибке Flow отменяется
launch {
    try {
        flow<Data> {
            throw Exception("Error")
        }.collect { data ->
            println(data)  // Не выполнится
        }
    } catch (e: Exception) {
        println("Caught: ${e.message}")
    }
}

Часто встречающиеся ошибки

Забыли catch после map:

flow<User> {
    emit(api.getUser())
}
.map { it.name }  // Может выброситься
.collect { name ->
    // catch здесь НЕ поймает ошибку из map если catch выше
}

Правильно:

flow<User> {
    emit(api.getUser())
}
.map { it.name }
.catch { exception ->
    emit("Error: ${exception.message}")
}
.collect { name ->
    updateUI(name)
}

На собеседовании

Покажите понимание:

  1. catch vs onCompletion — разница в поведении
  2. retryWhen — условное повторение
  3. timeout — защита от зависаний
  4. Многоуровневая обработка — где ловить ошибки (repository, viewmodel, UI)
  5. Result типы — Success, Failure, Loading
  6. try-catch в flow {} блоке — когда использовать

Правильная обработка исключений в Flow — признак опытного разработчика.

Как обрабатываются исключение в Flow? | PrepBro