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

Можно ли переопределить поведение при возникновении ошибки в корутине?

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

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Да, переопределить поведение при возникновении ошибки в корутине не только возможно, но и является важной практикой в разработке на Kotlin

Корутины предоставляют несколько механизмов для обработки ошибок, каждый из которых подходит для разных сценариев. Гибкость системы обработки исключений в корутинах — одна из их ключевых особенностей.

Основные механизмы обработки ошибок в корутинах

1. try-catch внутри корутины

Самый простой подход — использование стандартных блоков try-catch:

fun main() = runBlocking {
    val job = launch {
        try {
            // Потенциально опасная операция
            val result = riskyOperation()
            println("Успех: $result")
        } catch (e: Exception) {
            println("Ошибка перехвачена: ${e.message}")
        }
    }
    job.join()
}

suspend fun riskyOperation(): String {
    delay(100)
    throw RuntimeException("Что-то пошло не так")
}

2. CoroutineExceptionHandler для глобальной обработки

Для обработки неотловленных исключений на уровне корутины можно использовать CoroutineExceptionHandler:

fun main() = runBlocking {
    // Создаем обработчик исключений
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("Глобальный перехват: ${exception.message}")
        // Здесь можно отправить отчет об ошибке в Crashlytics
    }
    
    // Применяем обработчик к корутине
    val job = launch(exceptionHandler) {
        throw RuntimeException("Фатальная ошибка")
    }
    
    job.join()
}

Важно: CoroutineExceptionHandler работает только с корневыми корутинами (запущенными в launch) и не работает с async без вызова await().

3. Обработка ошибок в async/await

Для асинхронных операций с возвращаемым результатом используйте обработку при вызове await():

fun main() = runBlocking {
    val deferred = async {
        // Может выбросить исключение
        performNetworkRequest()
    }
    
    try {
        val result = deferred.await()
        println("Результат: $result")
    } catch (e: Exception) {
        println("Ошибка при await: ${e.message}")
    }
}

suspend fun performNetworkRequest(): String {
    delay(500)
    throw IOException("Сетевая ошибка")
}

4. supervisorScope для независимого выполнения

Когда нужно, чтобы ошибка в одной корутине не отменяла другие:

fun main() = runBlocking {
    supervisorScope {
        val child1 = launch {
            delay(100)
            throw RuntimeException("Ошибка в child1")
        }
        
        val child2 = launch {
            delay(200)
            println("Child2 выполнился, несмотря на ошибку в child1")
        }
        
        delay(300)
    }
}

Продвинутые стратегии обработки ошибок

Классификация исключений

Разделяйте исключения по типам для дифференцированной обработки:

sealed class AppException(message: String) : Exception(message) {
    class NetworkException(message: String) : AppException(message)
    class ValidationException(message: String) : AppException(message)
    class DatabaseException(message: String) : AppException(message)
}

fun main() = runBlocking {
    try {
        performBusinessLogic()
    } catch (e: AppException.NetworkException) {
        println("Проблемы с сетью: ${e.message}")
        showNetworkError()
    } catch (e: AppException.ValidationException) {
        println("Ошибка валидации: ${e.message}")
        showValidationError()
    } catch (e: Exception) {
        println("Неизвестная ошибка: ${e.message}")
        logError(e)
    }
}

Retry-логика с экспоненциальной задержкой

Реализация повторных попыток с прогрессивной задержкой:

suspend fun <T> retryWithBackoff(
    times: Int = 3,
    initialDelay: Long = 100,
    maxDelay: Long = 1000,
    factor: Double = 2.0,
    block: suspend () -> T
): T {
    var currentDelay = initialDelay
    repeat(times - 1) { attempt ->
        try {
            return block()
        } catch (e: Exception) {
            println("Попытка ${attempt + 1} неудачна: ${e.message}")
        }
        delay(currentDelay)
        currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
    }
    return block() // Последняя попытка
}

Рекомендации по обработке ошибок

  1. Всегда обрабатывайте исключения явно — не полагайтесь на стандартное поведение
  2. Разделяйте ответственность — бизнес-логика должна обрабатывать бизнес-исключения, технические слои — технические
  3. Используйте supervisorJob для независимых операций, которые не должны влиять друг на друга
  4. Логируйте ошибки для последующего анализа, но показывайте пользователю понятные сообщения
  5. Тестируйте обработку ошибок так же тщательно, как и успешные сценарии

Пример комплексной обработки

class UserRepository(
    private val apiService: ApiService,
    private val errorHandler: CoroutineExceptionHandler
) {
    suspend fun loadUserData(userId: String): Result<User> = supervisorScope {
        try {
            // Параллельные запросы
            val userDeferred = async { apiService.getUser(userId) }
            val postsDeferred = async { apiService.getUserPosts(userId) }
            
            val user = userDeferred.await()
            val posts = postsDeferred.await()
            
            Result.success(User(user, posts))
        } catch (e: IOException) {
            Result.failure(NetworkError("Проверьте подключение к интернету"))
        } catch (e: HttpException) {
            Result.failure(ServerError("Проблемы на сервере: ${e.code()}"))
        } catch (e: Exception) {
            withContext(errorHandler) {
                logError(e)
            }
            Result.failure(UnexpectedError("Неизвестная ошибка"))
        }
    }
}

Таким образом, корутины предоставляют богатый набор инструментов для обработки ошибок — от простых try-catch блоков до сложных стратегий с повторными попытками и глобальными обработчиками. Ключ к эффективной обработке ошибок — выбор подходящего механизма для конкретного контекста и соблюдение принципа "не падать молча".

Можно ли переопределить поведение при возникновении ошибки в корутине? | PrepBro