Kotlin CoroutinesMiddleTechnical

Что такое Deferred<T> и как использовать await()?

Deferred<T> — результат async-корутины; await() приостанавливает вызывающую корутину до получения значения, не блокируя поток. Несколько Deferred запускают задачи параллельно, а awaitAll() ждёт их все сразу.

Deferred<T> — отложённый результат корутины

Deferred<T> — это интерфейс из пакета kotlinx.coroutines, расширяющий Job. Он представляет корутину, которая в итоге вернёт значение типа T. Создаётся строителем async { ... }, который немедленно запускает корутину в фоне, а не блокирует поток.

Ключевые методы

  • await() — suspend-функция, приостанавливает текущую корутину до получения результата. Если корутина завершилась исключением, оно пробрасывается в вызывающем месте.
  • getCompleted() — возвращает результат без suspend; бросает исключение, если корутина ещё не завершилась.
  • isCompleted, isCancelled, isActive — свойства состояния, аналогичные Job.
  • cancel() — отменяет корутину, не ожидая результата.

Базовый пример

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferred: Deferred<Int> = async {
        delay(500)
        42
    }
    println("Waiting...")
    val result = deferred.await()   // приостанавливается, не блокирует поток
    println("Result: $result")      // Result: 42
}

Параллельная декомпозиция

Главная ценность async/await — запускать несколько независимых задач параллельно и потом объединять их результаты.

suspend fun fetchUserAndOrders(userId: String): Pair<User, List<Order>> =
    coroutineScope {                          // структурированный контекст
        val userDeferred = async { userRepo.find(userId) }
        val ordersDeferred = async { orderRepo.findByUser(userId) }
        userDeferred.await() to ordersDeferred.await()
    }

Здесь оба вызова БД идут параллельно. Общее время — max(t1, t2), а не t1 + t2.

await() с таймаутом

val result = withTimeout(2000L) {
    deferred.await()      // бросает TimeoutCancellationException при превышении
}

awaitAll для коллекций

val deferreds: List<Deferred<Int>> = ids.map { id ->
    async { fetchScore(id) }
}
val scores: List<Int> = deferreds.awaitAll()   // extension из kotlinx.coroutines

Ленивый запуск

val lazy = async(start = CoroutineStart.LAZY) {
    heavyComputation()
}
// корутина запускается только при вызове await() или start()
lazy.start()           // явный запуск без ожидания
val v = lazy.await()   // или запуск + ожидание

Подводные камни

  • Потеря исключения без await. Если не вызвать await(), исключение внутри async-блока не распространится автоматически — оно «зависнет» в Deferred и проявится только при вызове await(). Это отличие от launch, где исключение сразу идёт в CoroutineExceptionHandler.
  • Вызов await() вне coroutine scope. await() — suspend-функция; вызов из обычного кода не скомпилируется. Нужен runBlocking или другой coroutine builder.
  • async вне coroutineScope. Запуск async напрямую в GlobalScope лишает структурированного параллелизма: утечки корутин, невозможность отмены родителем.
  • Последовательный await вместо параллельного. val a = async { ... }.await(); val b = async { ... }.await() — это последовательно. Нужно сначала создать оба Deferred, потом оба await().
  • Игнорирование отмены. Если вызвать deferred.cancel(), а потом await(), будет брошено CancellationException. Обрабатывайте его явно или используйте runCatching.
  • Неправильная обработка ошибок при awaitAll. Если один из Deferred в списке упал, awaitAll() отменяет остальные и пробрасывает исключение первого упавшего.

Common mistakes

  • Объяснять «Deferred и await()» только как синтаксис и не описывать поведение runtime/compiler.
  • Игнорировать важный риск: Повторный await безопасен для результата, но ленивые Deferred и забытый await делают поток выполнения неочевидным.
  • Давать пример без edge case: отмены, null, recomposition, platform boundary или ошибки.

What the interviewer is testing

  • Формулирует суть темы «Deferred и await()» своими словами и связывает ее с кодом.
  • Называет механизм: await suspend-ится до завершения Deferred и либо возвращает T, либо выбрасывает сохраненное исключение.
  • Видит production-последствие: Повторный await безопасен для результата, но ленивые Deferred и забытый await делают поток выполнения неочевидным.

Sources

Related topics