> Какие проблемы возникают при оборачивании launch в try-catch в Kotlin Coroutines (Kotlin, Android)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Aston

Стек: Kotlin, Android

> Пример ответа

Короткий ответ

Проблема в том, что launch - это функция, которая возвращает Job и запускает корутину асинхронно, но сам вызов launch не выполняет тело корутины синхронно. Исключения, возникающие внутри корутины, не пробрасываются в вызывающий код через try-catch. Они распространяются через CoroutineExceptionHandler или отменяют родительскую корутину. try-catch вокруг launch ловит только ошибки при создании корутины, но не ошибки внутри неё.

Подробное объяснение

launch - это builder корутины, который возвращает Job немедленно. Тело корутины выполняется в диспетчере, часто в другом потоке или после переключения контекста. Поэтому исключение внутри тела корутины не может быть перехвачено try-catch в месте вызова launch - к моменту возникновения исключения стек вызовов уже другой.

Исключения в корутинах, запущенных через launch, обрабатываются следующим образом:

  • если есть CoroutineExceptionHandler в контексте - он получит исключение;
  • если нет - исключение передаётся в родительскую корутину, что приводит к отмене всей иерархии;
  • в Android это часто приводит к крашу приложения, так как исключение доходит до глобального обработчика.

Дополнительная проблема: try-catch вокруг launch может создать ложное ощущение безопасности. Разработчик думает, что ошибки обработаны, но на самом деле они уходят в глобальный обработчик. Это особенно опасно в production, где краши маскируются и сложно диагностируются.

Также стоит учитывать: если внутри корутины есть несколько точек с исключениями, try-catch снаружи не поможет ни для одной из них. Нужно использовать try-catch внутри корутины, либо runCatching, либо supervisorScope с обработкой ошибок на уровне дочерних корутин.

На практике

На Android это проявляется в виде необработанных исключений в фоновых задачах. Например, при загрузке данных из сети внутри launch - если сервер вернёт ошибку, приложение упадёт, несмотря на try-catch вокруг launch.

Правильный подход - обрабатывать исключения внутри корутины:

  • обернуть тело корутины в try-catch;
  • использовать CoroutineExceptionHandler для глобальной обработки;
  • для параллельных задач использовать supervisorScope + async с await внутри try-catch;
  • для UI-событий использовать viewModelScope.launch с обработкой ошибок внутри.

Также важно помнить: launch не возвращает результат. Если нужно получить результат или обработать ошибку - использовать async + await, где исключение пробрасывается в месте await и может быть перехвачено try-catch.

Пример кода

// Неправильно: try-catch не перехватит исключение из корутины
fun loadData() {
    try {
        viewModelScope.launch {
            val data = api.fetchData() // здесь может быть IOException
            updateUi(data)
        }
    } catch (e: Exception) {
        // этот блок не выполнится для ошибок внутри корутины
        showError(e)
    }
}

// Правильно: try-catch внутри корутины
fun loadData() {
    viewModelScope.launch {
        try {
            val data = api.fetchData()
            updateUi(data)
        } catch (e: Exception) {
            showError(e)
        }
    }
}

// Альтернатива: async + await
fun loadData() {
    viewModelScope.launch {
        try {
            val data = async { api.fetchData() }.await()
            updateUi(data)
        } catch (e: Exception) {
            showError(e)
        }
    }
}

Как отвечать на собеседовании

Начни с главного: launch не выполняет тело синхронно, поэтому try-catch снаружи бесполезен. Затем объясни механизм распространения исключений в корутинах: CoroutineExceptionHandler, отмена родительской корутины, глобальный обработчик. Приведи пример из практики - краш приложения при сетевой ошибке. Покажи правильные паттерны: try-catch внутри корутины, async + await, supervisorScope. Упомяни разницу между launch и async в контексте обработки ошибок. Если спросят про SupervisorJob - объясни, как он изолирует ошибки дочерних корутин.

Что проверяет интервьюер

  • понимание асинхронной природы корутин;
  • знание механизма распространения исключений в иерархии корутин;
  • умение выбирать правильный инструмент для обработки ошибок;
  • понимание разницы между launch и async;
  • знание CoroutineExceptionHandler и supervisorScope;
  • способность объяснить практические последствия неправильной обработки.

Типичные ошибки

  • утверждение, что try-catch вокруг launch работает;
  • игнорирование CoroutineExceptionHandler;
  • использование GlobalScope без обработки ошибок;
  • путаница между launch и async при обработке исключений;
  • забывание про отмену родительской корутины при необработанном исключении;
  • попытка перехватить исключение после launch через job.join() без try-catch внутри корутины.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью