Async/Await — це пара ключових слів для написання асинхронного коду в синхронному стилі, доступна в Swift з iOS 13 і в Kotlin з корутинами. Згідно з Apple Swift Documentation, 2026, Async/Await замінює ланцюжки callback і GCD, роблячи асинхронний код лінійним і читабельним. Ключове слово 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 компілятор зберігає контекст (локальні змінні, адресу повернення) і передає керування середовищу виконання. Коли результат готовий, середовище виконання відновлює контекст і продовжує виконання.
У Swift цей скінченний автомат реалізовано на рівні компілятора SIL (Swift Intermediate Language). У Kotlin — через механізм Continuation Passing Style (CPS): кожна suspend функція приймає прихований параметр Continuation, через який повертається результат. Обидва підходи гарантують безпечне відновлення без витоків пам'яті.
Callback-підхід вимагає передачі замикання, яке викликається після завершення операції. При трьох послідовних запитах виходить три рівні вкладеності з обробкою помилок на кожному. Async/Await перетворює ті самі три запити на три послідовні рядки з єдиним блоком catch.
GCD (DispatchQueue) вирішує проблему callback hell через serial черги та DispatchGroup, але залишається багатослівним. Для простого завдання — завантажити user, потім friends, потім UI — потрібне створення черг, груп та notify-блоків. Async/Await виконує те саме завдання трьома рядками в одній функції.
| Аспект | Callback | GCD | Async/Await |
|---|---|---|---|
| Вкладеність | Глибока (піраміда приреченості) | Середня (групи+notify) | Лінійна (flat) |
| Обробка помилок | Per-callback | Ручна | Єдина (try/catch) |
| Скасування | Ручне | Обмежене | Task.cancel() |
| Thread safety | Ручна | Через serial queue | Через MainActor |
За даними Apple WWDC 2024, Swift Concurrency async/await — рекомендований підхід для нового коду. GCD залишається для інтеграції з C-бібліотеками та специфічних низькорівневих сценаріїв обробки потоків.
Task — це основна одиниця асинхронної роботи в Swift. Task створюється в контексті існуючого актора або на довільному потоці. Усередині Task можна викликати async функції через await. TaskGroup дозволяє запускати кілька дочірніх завдань паралельно та збирати їхні результати — аналог DispatchGroup на стероїдах.
Actor — потокобезпечний reference type, що захищає свій стан від гонок даних. Компілятор гарантує, що доступ до властивостей actor можливий лише через async виклики або в межах actor isolation. Actor замінює serial DispatchQueue для захисту спільного змінюваного стану без ручних блокувань.
Принцип Structured Concurrency стверджує: кожне асинхронне завдання має батька, і батько не завершується, доки не завершаться всі його діти. Якщо завдання A запускає Task { await B() }, то A очікує B. Це запобігає витоку завдань і забезпечує передбачуваний життєвий цикл.
У 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 та серверній частині.
Розглянемо по два приклади для кожної мови: послідовне завантаження даних та паралельні запити з TaskGroup або async/await.
await призупиняє виконання до отримання даних користувача, потім — до отримання друзів. Жодних вкладених замикань.
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)
}
TaskGroup запускає завдання паралельно та збирає результати в масив. Порядок результатів може не збігатися з порядком запуску.
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
}
}
suspend функції можуть викликати інші suspend функції. Аналог Swift: послідовні виклики.
suspend fun loadProfile(): Profile {
val user = api.fetchUser()
val friends = api.fetchFriends(user.id)
return Profile(user, friends)
}
У Kotlin паралелізм досягається через coroutineScope з async для кожного запиту.
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())
}
Забутий Task { } — асинхронну функцію не можна викликати з синхронного контексту без обгортки в Task. У Swift це компілюється з помилкою. У Kotlin потрібен запуск через lifecycleScope або viewModelScope. Спроба викликати suspend функцію зі звичайної функції не скомпілюється.
Не кожну функцію потрібно робити асинхронною. CPU-інтенсивні завдання без I/O не виграють від async — їх краще виконувати на DispatchQueue з .userInitiated QoS. Async/Await оптимізований для I/O-bound операцій: мережа, диск, очікування.
Використання .result на головному потоці (синхронне очікування результату async функції) призводить до блокування. У Swift Task.synchronousWait не рекомендується Apple. У Kotlin runBlocking на головному потоці — антипатерн, використовуйте lifecycleScope.
Якщо Task не зберігається у властивості класу, після виходу з контексту він скасовується. У Swift Task є scoped — його життєвий цикл прив'язаний до контексту створення. Зберігайте Task у властивості при необхідності довготривалої операції.
Часті запитання
Async/Await — мовна конструкція, що не вимагає створення черг. DispatchQueue — це системний API для керування потоками. Async/await компілюється в скінченний автомат на рівні мови, GCD — в syscall на рівні ОС.
Ні — await призупиняє функцію, але звільняє потік для інших завдань. Після завершення операції функція відновлюється на будь-якому доступному потоці з пулу.
Так — через Task.cancel() у Swift або Job.cancel() у Kotlin. Скасування поширюється на всі дочірні завдання згідно з Structured Concurrency. Код сам перевіряє прапорець скасування через Task.isCancelled або ensureActive().
MainActor — це actor, чиї властивості та методи завжди виконуються на головному потоці. Позначаючи функцію @MainActor, ви гарантуєте, що оновлення UI відбуваються на правильному потоці, замінюючи DispatchQueue.main.async.
Async/Await доступний у Swift (iOS 13+), Kotlin (через kotlinx.coroutines), Dart (Flutter), JavaScript/TypeScript, Python, C#, Rust та Go (через goroutines із синтаксичним цукром).
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також