Как обрабатываются ошибки в Kotlin Coroutines
Что проверяют
Понимаете ли вы, что сбой распространяется по иерархии, и знаете ли особый статус отмены.
Как отвечать
Обработка зависит от того, чем запущена корутина.
launch: исключение выбрасывается сразу и поднимается вверх по иерархии. Оно отменяет родителя, а тот отменяет всех остальных детей. Дойдя до корня, попадает в CoroutineExceptionHandler области, а если его нет, в обработчик по умолчанию, что обычно означает падение приложения.
async: исключение сохраняется в Deferred и выбрасывается при вызове await. Поэтому оборачивать в try/catch надо await, а не сам async.
Ключевые правила:
try/catchвнутри корутины работает как обычно и это предпочтительный способ локальной обработки. Обработчик уровня области это последний рубеж, а не основной инструмент;CoroutineExceptionHandlerдействует только на корневой корутине области. Переданный в дочернийlaunch, он молча не сработает;SupervisorJobиsupervisorScopeне дают сбою подниматься вверх, поэтому падение одной загрузки не убивает соседние. Именно так устроенviewModelScope.
Отдельная и самая коварная тема это CancellationException. Это не ошибка, а штатный сигнал отмены. Если поймать его общим catch (e: Exception) и не пробросить дальше, отмена сломается: корутина продолжит работать после отмены области. Поэтому либо ловите конкретные типы, либо пробрасывайте отмену обратно. По той же причине опасен runCatching, он ловит всё подряд.
Во Flow своя механика: оператор catch перехватывает исключения выше по потоку и не трогает код в collect, retry и retryWhen дают повторы, onCompletion выполняется в любом случае.
Где спотыкаются
Вешают обработчик на дочернюю корутину.
Ловят CancellationException и глотают её.
Оборачивают try/catch вокруг async вместо await.
Считают, что supervisorScope защищает вложенные корутины любой глубины.