← Назад к вопросам
Как обрабатываются исключение в 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)
}
На собеседовании
Покажите понимание:
- catch vs onCompletion — разница в поведении
- retryWhen — условное повторение
- timeout — защита от зависаний
- Многоуровневая обработка — где ловить ошибки (repository, viewmodel, UI)
- Result типы — Success, Failure, Loading
- try-catch в flow {} блоке — когда использовать
Правильная обработка исключений в Flow — признак опытного разработчика.