Отличная статья! Спасибо за обзор Kotlin Flows. У меня есть небольшая проблема с использованием функций приостановки в Flow. Мне было интересно, что вы думаете по этому вопросу.

Согласно рекомендациям по сопрограммам, функция приостановки никогда не должна полагаться на контекст выполнения вызывающей стороны. Если он выполняет длительную операцию, он должен указать соответствующий контекст самостоятельно, используя withContext, чтобы никогда не возникала ситуация, когда вызывающий объект выйдет из строя из-за неподходящего диспетчера. Это отлично работает для простых сопрограмм, но мне интересно, хорошо ли этот подход работает с Flow.

Позвольте мне привести вам один тривиальный пример, который описывает мою проблему. Предположим, у нас есть поток элементов (itemsFlow), для которого мы хотим загрузить данные с сервера, а затем выполнить некоторые длительные вычисления. Этот пример может быть реализован двумя способами.

#1 Использование withContext

fun computedDataFlow(): Flow<ComputedData> =
    itemsFlow
        .map(::fetchData)
        .map(::computeData)
suspend fun fetchData(item: Item): Data =
    withContext(Dispatchers.IO) {
        // implementation here
    }
suspend fun computeData(data:Data): ComputedData =
    withContext(Dispatchers.Default) {
        // implementation here
    }

#2 Использование оператора flowOn

fun computedDataFlow(): Flow<ComputedData> =
    itemsFlow
        .map(::fetchData)        
        .flowOn(Dispatchers.IO)
        .map(::computeData)
        .flowOn(Dispatchers.Default)
suspend fun fetchData(item: Item): Data =
        // implementation here
suspend fun computeData(data:Data): ComputedData =
        // implementation here

Мне интересно, эквивалентны ли эти реализации или есть ли какие-то затраты на то, чтобы сделать это так или иначе? Есть ли какие-то скрытые затраты на смешение подхода withContext в Flow? Если мы будем следовать соглашению о простых сопрограммах, то есть ли причина когда-либо использовать оператор flowOn? Какой из версий вы бы посоветовали нам следовать?

С одной стороны, версия №1 безопаснее, так как эти функции также можно вызывать из разных контекстов. С другой стороны, версия №2 для меня более читабельна, так как она очень знакома с реализацией в RxJava.

Мне интересно, что бы вы посоветовали на эту тему.