Как реализовать метод который загружает асинхронную загрузку из 2 источников?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация асинхронной загрузки из двух источников
Для реализации метода, который выполняет асинхронную загрузку из двух источников, необходимо выбрать правильный подход в зависимости от требований: параллельная или последовательная загрузка, обработка ошибок и тип возвращаемых данных. Рассмотрим основные стратегии с использованием Kotlin Coroutines и Kotlin Flow.
Основные подходы
1. Параллельная загрузка с использованием async/await
Этот подход оптимален, когда источники независимы и могут загружаться одновременно.
suspend fun loadFromTwoSourcesParallel(
source1: suspend () -> Result<Data>,
source2: suspend () -> Result<Data>
): Pair<Result<Data>, Result<Data>> {
val deferred1 = CoroutineScope(Dispatchers.IO).async { source1() }
val deferred2 = CoroutineScope(Dispatchers.IO).async { source2() }
return Pair(deferred1.await(), deferred2.await())
}
2. Последовательная загрузка с обработкой ошибок
Когда второй запрос зависит от результата первого или нужен контроль порядка:
suspend fun loadFromTwoSourcesSequential(): CombinedResult {
return try {
val firstResult = fetchFirstSource()
val secondResult = fetchSecondSource(firstResult.data)
CombinedResult.Success(firstResult, secondResult)
} catch (e: Exception) {
CombinedResult.Error(e)
}
}
Расширенная реализация с Flow
Для реактивного подхода с возможностью обработки промежуточных состояний используйте Kotlin Flow:
fun loadFromTwoSourcesFlow(): Flow<LoadingState> = flow {
emit(LoadingState.Loading)
val results = listOf(
async { fetchFirstSource() },
async { fetchSecondSource() }
).awaitAll()
when {
results.all { it.isSuccess } -> {
val data1 = results[0].getOrNull()
val data2 = results[1].getOrNull()
emit(LoadingState.Success(data1 to data2))
}
results.any { it.isFailure } -> {
val error = results.firstOrNull { it.isFailure }?.exceptionOrNull()
emit(LoadingState.Error(error ?: UnknownError()))
}
}
}.flowOn(Dispatchers.IO)
Продвинутые техники
Комбинирование с таймаутами и отменой
suspend fun loadWithTimeout(timeoutMs: Long = 5000) =
withTimeout(timeoutMs) {
supervisorScope {
val first = async { loadFirstSource() }
val second = async { loadSecondSource() }
try {
val result1 = first.await()
val result2 = second.await()
mergeResults(result1, result2)
} catch (e: TimeoutCancellationException) {
throw SourceTimeoutException("Loading timeout exceeded")
}
}
}
Ключевые аспекты реализации
- Распределение по потокам: Используйте
Dispatchers.IOдля сетевых операций иDispatchers.Mainдля обновления UI - Обработка ошибок: Реализуйте стратегию fallback или кэширование при сбое одного из источников
- Отмена операций: Учитывайте жизненный цикл компонентов через
CoroutineScope.launchсJob - Кэширование: Сохраняйте результаты для оптимизации повторных запросов
- Тестирование: Используйте
TestCoroutineDispatcherдля модульного тестирования
Пример полной реализации
class DataLoader(
private val localSource: LocalDataSource,
private val remoteSource: RemoteDataSource
) {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
fun loadCombinedData(): Flow<Resource<CombinedData>> = channelFlow {
send(Resource.Loading)
try {
val localDeferred = scope.async(Dispatchers.IO) {
localSource.getData()
}
val remoteDeferred = scope.async(Dispatchers.IO) {
remoteSource.fetchData()
}
val localResult = localDeferred.await()
val remoteResult = remoteDeferred.await()
val combined = CombinedData(
local = localResult,
remote = remoteResult,
timestamp = System.currentTimeMillis()
)
send(Resource.Success(combined))
} catch (e: Exception) {
send(Resource.Error(e, getCachedData()))
}
}
fun cancel() {
scope.cancel()
}
}
Выбор конкретной реализации зависит от бизнес-требований: нужна ли параллельная загрузка, как обрабатывать частичные сбои, требуется ли поддержка промежуточных состояний. Наиболее гибким и современным подходом является использование Kotlin Flow с корутинами, что обеспечивает реактивность, отмену операций и чистую архитектуру.