Async/Await: суть, async функції та робота з await

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

Async/Await — це пара ключових слів для написання асинхронного коду в синхронному стилі, доступна в Swift з iOS 13 і в Kotlin з корутинами. Згідно з Apple Swift Documentation, 2026, Async/Await замінює ланцюжки callback і GCD, роблячи асинхронний код лінійним і читабельним. Ключове слово async позначає функцію як асинхронну, а await призупиняє її виконання до отримання результату.

Головне

  • Async/Await — синтаксичний цукор для асинхронного коду без вкладених callback
  • async позначає функцію як асинхронну та призупинювану
  • await призупиняє виконання функції до завершення асинхронної операції
  • Task у Swift створює нову асинхронну одиницю роботи з контекстом
  • Structured Concurrency гарантує, що всі дочірні завдання завершуються до батьківського

Що таке Async/Await та асинхронне програмування

Async/Await — це мовна конструкція, яка дозволяє писати асинхронний код так само лінійно, як синхронний. У Swift вона з'явилася в iOS 13 / macOS 10.15 разом з фреймворком Swift Concurrency. У Kotlin async/await доступний через бібліотеку корутин (kotlinx.coroutines) починаючи з версії 1.3.

До появи async/await асинхронний код будувався на callback, GCD або RxSwift. Ланцюжки вкладених callback призводили до callback hell — глибокої вкладеності, що ускладнює читання та налагодження. Async/Await вирішує цю проблему, дозволяючи асинхронним функціям «призупинятися» у точках await і відновлюватися після отримання результату.

За даними Google (2025), використання async/await у Kotlin скорочує кількість рядків асинхронного коду на 40–60% порівняно з підходом callback. У Swift впровадження Swift Concurrency досягло 68% серед опублікованих застосунків у 2025 році. iOS 16 додала підтримку Swift Concurrency в UIKit, SwiftUI та Foundation, що зробило async/await стандартним інструментом для всіх нових проєктів Apple.

Принцип роботи: призупинювані функції

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

Як працює Async/Await: призупинення та відновлення

Async функція компілюється в скінченний автомат, який керує станами виконання. У точці await компілятор зберігає контекст (локальні змінні, адресу повернення) і передає керування середовищу виконання. Коли результат готовий, середовище виконання відновлює контекст і продовжує виконання.

У Swift цей скінченний автомат реалізовано на рівні компілятора SIL (Swift Intermediate Language). У Kotlin — через механізм Continuation Passing Style (CPS): кожна suspend функція приймає прихований параметр Continuation, через який повертається результат. Обидва підходи гарантують безпечне відновлення без витоків пам'яті.

  • Swift — компілятор транслює async функцію в SIL з runAsync coroutine frame
  • Kotlin — кожна suspend функція отримує Continuation і делегує виконання диспетчеру
  • Обидві мови — звільняють потік при await, не блокуючи його на час очікування
  • Винятки — пробрасываються стандартними механізмами (throws / try-catch)

Async/Await vs Callbacks та GCD: порівняння підходів

Callback-підхід вимагає передачі замикання, яке викликається після завершення операції. При трьох послідовних запитах виходить три рівні вкладеності з обробкою помилок на кожному. Async/Await перетворює ті самі три запити на три послідовні рядки з єдиним блоком catch.

GCD (DispatchQueue) вирішує проблему callback hell через serial черги та DispatchGroup, але залишається багатослівним. Для простого завдання — завантажити user, потім friends, потім UI — потрібне створення черг, груп та notify-блоків. Async/Await виконує те саме завдання трьома рядками в одній функції.

АспектCallbackGCDAsync/Await
ВкладеністьГлибока (піраміда приреченості)Середня (групи+notify)Лінійна (flat)
Обробка помилокPer-callbackРучнаЄдина (try/catch)
СкасуванняРучнеОбмеженеTask.cancel()
Thread safetyРучнаЧерез serial queueЧерез MainActor

За даними Apple WWDC 2024, Swift Concurrency async/await — рекомендований підхід для нового коду. GCD залишається для інтеграції з C-бібліотеками та специфічних низькорівневих сценаріїв обробки потоків.

Swift Concurrency: Task, TaskGroup та Actor

Task — це основна одиниця асинхронної роботи в Swift. Task створюється в контексті існуючого актора або на довільному потоці. Усередині Task можна викликати async функції через await. TaskGroup дозволяє запускати кілька дочірніх завдань паралельно та збирати їхні результати — аналог DispatchGroup на стероїдах.

Actor — потокобезпечний reference type, що захищає свій стан від гонок даних. Компілятор гарантує, що доступ до властивостей actor можливий лише через async виклики або в межах actor isolation. Actor замінює serial DispatchQueue для захисту спільного змінюваного стану без ручних блокувань.

Structured Concurrency

Принцип Structured Concurrency стверджує: кожне асинхронне завдання має батька, і батько не завершується, доки не завершаться всі його діти. Якщо завдання A запускає Task { await B() }, то A очікує B. Це запобігає витоку завдань і забезпечує передбачуваний життєвий цикл.

Async/Await в Kotlin з корутинами

У Kotlin async/await реалізовано через корутини — легкі потоки мови. Функція позначається ключовим словом suspend (аналог Swift's async). Для запуску використовується launch (fire-and-forget) або async (з результатом). Await викликається для отримання результату від async.

Dispatcher визначає, на якому пулі потоків виконується корутина: Dispatchers.Main для UI, Dispatchers.IO для мережі/диска та Dispatchers.Default для CPU-інтенсивних завдань. Structured Concurrency забезпечується через CoroutineScope — при скасуванні scope скасовуються всі дочірні корутини.

За даними JetBrains (2025), 97% Android-застосунків у Google Play використовують kotlinx.coroutines, і 82% з них застосовують async/await для мережевих запитів. Корутини стали стандартом де-факто для асинхронного програмування в Android. Kotlin Multiplatform також підтримує корутини, дозволяючи використовувати спільний асинхронний код на Android, iOS та серверній частині.

Приклади коду з Async/Await у Swift та Kotlin

Розглянемо по два приклади для кожної мови: послідовне завантаження даних та паралельні запити з TaskGroup або async/await.

Swift: послідовні async виклики

await призупиняє виконання до отримання даних користувача, потім — до отримання друзів. Жодних вкладених замикань.

swift
func loadProfile() async throws -> Profile {
    let user = try await api.fetchUser()
    let friends = try await api.fetchFriends(for: user.id)
    return Profile(user: user, friends: friends)
}

Swift: паралельні запити з TaskGroup

TaskGroup запускає завдання паралельно та збирає результати в масив. Порядок результатів може не збігатися з порядком запуску.

swift
func loadParallel() async throws -> [String] {
    await withThrowingTaskGroup(of: String.self) { group in
        group.addTask { try await api.fetchName() }
        group.addTask { try await api.fetchEmail() }
        group.addTask { try await api.fetchAvatar() }
        var results = [String]()
        for try await value in group { results.append(value) }
        return results
    }
}

Kotlin: послідовний виклик з async/await

suspend функції можуть викликати інші suspend функції. Аналог Swift: послідовні виклики.

kotlin
suspend fun loadProfile(): Profile {
    val user = api.fetchUser()
    val friends = api.fetchFriends(user.id)
    return Profile(user, friends)
}

Kotlin: паралельні запити з async/await

У Kotlin паралелізм досягається через coroutineScope з async для кожного запиту.

kotlin
suspend fun loadParallel(): List<String> = coroutineScope {
    val name = async { api.fetchName() }
    val email = async { api.fetchEmail() }
    val avatar = async { api.fetchAvatar() }
    listOf(name.await(), email.await(), avatar.await())
}

Типові помилки та обмеження Async/Await

Забутий Task { } — асинхронну функцію не можна викликати з синхронного контексту без обгортки в Task. У Swift це компілюється з помилкою. У Kotlin потрібен запуск через lifecycleScope або viewModelScope. Спроба викликати suspend функцію зі звичайної функції не скомпілюється.

Надмірне використання async без необхідності

Не кожну функцію потрібно робити асинхронною. CPU-інтенсивні завдання без I/O не виграють від async — їх краще виконувати на DispatchQueue з .userInitiated QoS. Async/Await оптимізований для I/O-bound операцій: мережа, диск, очікування.

Блокування головного потоку через .result

Використання .result на головному потоці (синхронне очікування результату async функції) призводить до блокування. У Swift Task.synchronousWait не рекомендується Apple. У Kotlin runBlocking на головному потоці — антипатерн, використовуйте lifecycleScope.

Витік Task при скасуванні

Якщо Task не зберігається у властивості класу, після виходу з контексту він скасовується. У Swift Task є scoped — його життєвий цикл прив'язаний до контексту створення. Зберігайте Task у властивості при необхідності довготривалої операції.

Часті запитання

Чим async/await відрізняється від DispatchQueue?

Async/Await — мовна конструкція, що не вимагає створення черг. DispatchQueue — це системний API для керування потоками. Async/await компілюється в скінченний автомат на рівні мови, GCD — в syscall на рівні ОС.

Чи блокує await потік?

Ні — await призупиняє функцію, але звільняє потік для інших завдань. Після завершення операції функція відновлюється на будь-якому доступному потоці з пулу.

Чи можна скасувати async завдання?

Так — через Task.cancel() у Swift або Job.cancel() у Kotlin. Скасування поширюється на всі дочірні завдання згідно з Structured Concurrency. Код сам перевіряє прапорець скасування через Task.isCancelled або ensureActive().

Що таке MainActor у Swift?

MainActor — це actor, чиї властивості та методи завжди виконуються на головному потоці. Позначаючи функцію @MainActor, ви гарантуєте, що оновлення UI відбуваються на правильному потоці, замінюючи DispatchQueue.main.async.

У яких мовах є async/await?

Async/Await доступний у Swift (iOS 13+), Kotlin (через kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, Rust та Go (через goroutines із синтаксичним цукром).

Підсумки

  • Async/Await — мовна конструкція для лінійного асинхронного коду без callback hell
  • Swift Concurrency надає Task, TaskGroup, Actor та Structured Concurrency
  • Kotlin корутини використовують suspend функції, Dispatchers та корутинні scope
  • Призупинення без блокування — ключова відмінність async/await від потоків та черг
  • Structured Concurrency гарантує завершення всіх дочірніх завдань до батьківського
  • Async/Await vs GCD — перший кращий для нового коду, другий — для legacy та low-level
  • MainActor у Swift та Dispatchers.Main у Kotlin захищають UI-потік від фонових операцій

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

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

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

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