async: що це таке, Deferred та паралельні корутини як працюють

Автор: IT Sectr Опубліковано: 2026-06-22 Час читання: 8 хв

async — це Coroutine Builder в Kotlin, який запускає корутину та повертає об’єкт Deferred<T> для отримання результату асинхронної операції. async дозволяє виконувати кілька завдань паралельно та агрегувати їхні результати через await(). Згідно з посібником JetBrains, 2024, async є прийнятним способом організації паралельних обчислень у корутинах.

Головне

  • async — білдер корутин, що повертає Deferred<T> з результатом асинхронної операції
  • Deferred — асинхронна обіцянка, що успадковує Job, з методом await() для отримання результату
  • async дозволяє виконувати незалежні завдання паралельно, скорочуючи загальний час
  • await() — suspend-функція, що призупиняє корутину до отримання результату без блокування
  • Структурована конкурентність з async гарантує скасування всіх паралельних завдань при помилці

Що таке async в Kotlin Coroutines?

async — це функція розширення CoroutineScope, яка створює корутину з поверненням результату. На відміну від launch, async повертає Deferred<T> — об’єкт, що представляє майбутнє значення. Корутина, запущена через async, виконується конкурентно з іншими корутинами, що дозволяє паралелізувати обчислення.

async використовується, коли результат асинхронної операції потрібний для подальшої обробки: завантаження даних з декількох джерел, пакетна обробка колекцій, паралельні запити до різних API. Deferred створюється негайно, а результат стає доступним після завершення корутини.

kotlin
import kotlinx.coroutines.*

suspend fun String.delayedValue(): String {
    delay(1000L)
    return this
}

fun main() = runBlocking {
    val deferred: Deferred<String> = async { "Hello".delayedValue() }
    println(deferred.await())
}

Синтаксис async та Deferred

Сигнатура async аналогічна launch: ті ж параметри CoroutineContext, CoroutineStart та блок suspend. Відмінність у типі повернення — Deferred<T> замість Job, де T — це тип результату, що повертається останнім виразом блоку.

kotlin
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

Deferred<T> — інтерфейс, що успадковує Job та додає методи await(), getCompleted(), getCompletionExceptionOrNull(). await() є suspend-функцією і може бути викликана лише всередині корутини або іншої suspend-функції. getCompleted() — неблокуючий метод, який викидає виняток, якщо корутина ще не завершилась.

Паралельні завдання з async

Основна перевага async — можливість виконувати завдання паралельно. Замість послідовного очікування кожної операції (загальний час = сума часів), async запускає корутини одночасно, скорочуючи загальний час до тривалості найдовшої операції.

Паралельне завантаження даних

Типовий сценарій — завантаження профілю користувача, його налаштувань та історії замовлень трьома паралельними async-запитами. Після завершення всіх трьох результати об’єднуються в єдину модель даних.

kotlin
suspend fun loadUserDashboard(userId: String): Dashboard = coroutineScope {
    val profile = async { api.getProfile(userId) }
    val settings = async { api.getSettings(userId) }
    val orders = async { api.getRecentOrders(userId) }

    Dashboard(
        profile = profile.await(),
        settings = settings.await(),
        orders = orders.await()
    )
}

Функція coroutineScope запускає дочірні корутини та очікує їхнього завершення перед поверненням результату. Весь блок виконується паралельно, а не послідовно.

Обробка результатів Deferred

Deferred підтримує кілька методів для роботи з результатом. Крім await(), доступні методи для перевірки стану, отримання завершеного значення та агрегації кількох Deferred через утилітні функції Kotlin Coroutines.

МетодОписSuspend?
await()Призупиняє корутину до отримання результатуТак
getCompleted()Повертає результат, якщо корутина завершилась (інакше Exception)Ні
getCompletionExceptionOrNull()Повертає виняток при помилці або nullНі
isCompletedПеревіряє, чи завершилася корутинаНі

Для агрегації списку Deferred використовується awaitAll() — функція, що призупиняє виконання до завершення всіх Deferred у колекції. Якщо хоча б один завершився з помилкою, awaitAll() викидає виняток.

kotlin
suspend fun <T> loadAll(requests: List<suspend () -> T>): List<T> {
    return coroutineScope {
        val deferreds = requests.map { async { it() } }
        deferreds.awaitAll()
    }
}

Відмінності async від launch

Вибір між async та launch залежить від того, чи потрібен результат корутини. launch повертає Job та підходить для операцій fire-and-forget, async повертає Deferred та використовується для завдань, що повертають дані. За синтаксисом та параметрами білдери ідентичні.

  • launch → Job (немає результату), async → Deferred<T> (результат T)
  • launch — завдання fire-and-forget (логування, кешування, відправлення аналітики)
  • async — паралельні обчислення (завантаження з декількох API, пакетна обробка)
  • Обидва підтримують структуровану конкурентність та параметри CoroutineStart
  • await() доступний лише для Deferred, launch не надає результату

Важливе правило: ніколи не використовуйте async для fire-and-forget. Якщо результат корутини не потрібний, використовуйте launch. async створює накладні витрати на створення Deferred, що не виправдано в таких сценаріях.

Приклади async в мобільній розробці

В Android async застосовується для паралельних операцій всередині ViewModel та UseCases. Типові сценарії: одночасне завантаження профілю та стрічки новин, отримання погоди з кількох метеослужб, пакетна синхронізація даних з сервером.

kotlin
class HomeViewModel : ViewModel() {
    fun loadHomeScreen() {
        viewModelScope.launch {
            val userData = async(Dispatchers.IO) { repository.getUserData() }
            val newsFeed = async(Dispatchers.IO) { repository.getNewsFeed() }
            val notifications = async(Dispatchers.IO) { repository.getNotifications() }

            val state = HomeState(
                user = userData.await(),
                news = newsFeed.await(),
                unread = notifications.await()
            )
            _uiState.update { state }
        }
    }
}

Рекомендується обгортати групу async в coroutineScope або supervisorScope для ізоляції помилок. supervisorScope дозволяє іншим async-корутинам продовжувати виконання навіть якщо одна завершилася з помилкою — корисно для некритичних операцій.

Часто запитувані питання

Чи можна використовувати async без await?

Можна, але безглуздно. async без await() запускає корутину, але її результат втрачається. Якщо результат не потрібний, використовуйте launch — він не створює Deferred та є ефективнішим за пам’яттю та продуктивністю.

Як async обробляє винятки?

Виняток всередині async зберігається в Deferred. Виклик await() пробрасює цей виняток. Для перевірки без пробрасювання використовуйте getCompletionExceptionOrNull(). При використанні coroutineScope помилка скасовує всі дочірні корутини.

Що таке awaitAll в Kotlin Coroutines?

awaitAll() — функція розширення для Iterable<Deferred<T>>, яка призупиняє корутину до завершення всіх Deferred. Повертає список результатів. Якщо хоча б один Deferred невдалий, він викидає виняток та скасовує інші.

Чим async відрізняється від GlobalScope.async?

Так. async — це функція розширення CoroutineScope, прив’язана до області видимості. GlobalScope.async створює корутину без прив’язки до життєвого циклу — вона може виконуватися необмежено. GlobalScope не рекомендується в Android через ризик витоків.

Скільки async-корутин можна запустити для паралельних запитів?

Кількість визначається диспетчером: Dispatchers.IO підтримує до 64 одночасних потоків, Dispatchers.Default — до кількості ядер CPU. Для масових операцій використовуйте обмежений паралелізм через Semaphore або mapNotNull з awaitAll.

Підсумки

  • async — білдер корутин, що повертає Deferred<T> для асинхронного отримання результату
  • Deferred успадковує Job та додає методи await(), getCompleted(), getCompletionExceptionOrNull()
  • Паралельне виконання з async скорочує загальний час до тривалості найдовшої операції
  • awaitAll() — утиліта для очікування всіх Deferred у колекції
  • coroutineScope — рекомендована оболонка для групи async з гарантією скасування
  • Не використовуйте async для fire-and-forget — натомість використовуйте launch
  • В Android async використовується для паралельного завантаження в ViewModel з viewModelScope

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також