Future/Promise — це патерн асинхронного програмування, що представляє контейнер для результату ще не завершеної операції. За даними Dart Documentation, 2025, Future — read-only результат відкладених обчислень, Promise — write-only контракт, який цей результат заповнює. Future використовується в Dart, Java, Scala та C++ для ланцюжків then/catch.
Головне
Future/Promise — це пара взаємопов’язаних об’єктів, які розділяють асинхронний результат на дві ролі. Promise може записати результат (resolve) або помилку (reject). Future може тільки прочитати його — через callback (then, catchError) або через await. Такий розподіл гарантує, що споживач не може змінити результат, тільки використати його.
Мова Dart використовує Future як основний механізм асинхронності. Future<T> — це контракт на отримання значення типу T у майбутньому. Future може бути в одному з трьох станів: unresolved (операція виконується), resolved with value (успішно завершена) або resolved with error (завершена з помилкою). Після переходу в resolved стан Future не змінюється.
Promise в JavaScript — це аналог Dart-ового Future, але з більш багатим API. Promise приймає екзекутор — функцію з параметрами resolve та reject. Статичні методи Promise.all, Promise.race, Promise.allSettled дозволяють комбінувати кілька промісів. Сучасні додатки використовують async/await синтаксис поверх Promise.
Future працює на основі event loop — циклу подій, який обробляє асинхронні операції без блокування основного потоку. Коли викликається асинхронна функція (наприклад, http.get), вона одразу повертає Future. Реальний запит виконується у фоні, а event loop продовжує обробляти інші події. Після завершення операції Future переходить у resolved стан і викликається then.
Event loop — ключовий механізм, на якому побудовані Dart та JavaScript. Event loop підтримує дві черги: microtask queue (короткі завдання, виконуються перед наступною подією) та event queue (події IO, таймери). Future.then розміщує callback у microtask queue, Future.microtask — пріоритетне виконання до наступного кадру.
Ланцюжки then() дозволяють послідовно обробляти результати асинхронних операцій без callback hell. Кожен then() отримує результат попереднього кроку та повертає новий Future. catchError() в кінці ланцюжка обробляє будь-яку помилку, що виникла на будь-якому кроці. Це робить асинхронний код лінійним і читабельним.
У Java CompletableFuture розширює концепцію Future/Promise. Метод supplyAsync() запускає асинхронне завдання в ForkJoinPool.commonPool(). thenApply() трансформує результат, thenCompose() — розгортає вкладений CompletableFuture. exceptionally() обробляє помилку з поверненням значення за замовчуванням. allOf() та anyOf() працюють як Promise.all та Promise.race в JavaScript. CompletableFuture підтримує ручне завершення через complete() та completeExceptionally() — це аналог Promise resolve/reject.
Promise — це контракт з одним із трьох станів: pending (очікування), fulfilled (завершено успішно) або rejected (завершено з помилкою). Після переходу в fulfilled або rejected стан Promise ніколи не змінюється — це гарантує імутабельність результату для всіх споживачів. Метод .then() викликається асинхронно після переходу в будь-який завершений стан.
Promise.all очікує завершення всіх переданих промісів та повертає масив результатів у тому ж порядку. Якщо один проміс завершився помилкою — весь Promise.all переходить у rejected. Promise.race повертає результат першого завершеного промісу (успіх або помилка). Promise.allSettled чекає всі проміси незалежно від помилок — повертає статус кожного.
Chaining — ключова перевага Promise перед callback. Кожен .then() повертає новий Promise, дозволяючи вибудовувати ланцюжки довільної довжини. Помилка в будь-якій ланці ланцюжка пробрасується до найближчого .catch(). Метод finally() виконує код після завершення ланцюжка незалежно від результату — для очищення ресурсів.
Перший приклад — створення Future через async-функцію та обробка через then/catchError:
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 1))
return "Дані завантажено"
}
fetchData()
.then((result) => print(result))
.catchError((error) => print("Помилка: $error"))
Другий приклад — комбінування кількох Future через Future.wait:
Future<List<String>> loadAll() async {
final futures = [
fetchFromApi("/users"),
fetchFromApi("/posts"),
fetchFromApi("/comments")
]
final results = await Future.wait(futures)
return results
}
Третій приклад — Promise в JavaScript з ручним створенням та ланцюжком then:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Готово")
}, 1000)
})
promise
.then(value => console.log(value))
.catch(err => console.error(err))
Future/Promise відрізняється від callback-стилю тим, що повертає об’єкт-контракт замість того, щоб приймати функцію зворотного виклику аргументом. Callback-стиль призводить до вкладеності (callback hell), тоді як Future дозволяє вибудовувати плоскі ланцюжки then(). async/await синтаксис робить код з Future ще більш лінійним, схожим на синхронний.
Future представляє рівно один асинхронний результат — одна емісія, після якої Future завершується. На відміну від Observable (Rx, ReactiveX) або Flow (Kotlin Coroutines), Future не підтримує множинні значення в часі. Якщо потрібен стрім подій — використовуйте Stream в Dart, Observable в Rx або Flow в Kotlin.
CompletableFuture в Java — аналог Promise з розширеним API: supplyAsync, thenApply, thenCompose, exceptionally. CompletableFuture дозволяє об’єднувати кілька асинхронних операцій через allOf, anyOf, а також задавати власний Executor для виконання. Scala Future використовує ExecutionContext для керування пулом потоків та підтримує композицію через for-comprehension.
У Dart патерн Future відрізняється від JavaScript Promise тим, що Dart має строгу типізацію: Future<String> гарантує тип значення, що повертається. async-функції в Dart зобов’язані повертати Future, і компілятор перевіряє, що всі шляхи повертають коректний тип. Future.wait еквівалентний Promise.all — приймає Iterable<Future<T>> та повертає Future<List<T>>. Future.delayed та Future.error — зручні фабрики для тестування асинхронних сценаріїв.
Future/Promise реалізований у Python через concurrent.futures.Future та asyncio.Future. concurrent.futures.ThreadPoolExecutor.submit() повертає Future, у якого можна викликати result() — блокуючий виклик. asyncio.Future — це await-сумісний контейнер для асинхронного результату в Asyncio. asyncio.gather() паралельно запускає кілька корутин та повертає список результатів, аналогічно Promise.all у JavaScript. Python також використовує callback-based asyncio.ensure_future та ланцюжки add_done_callback для композиції асинхронних операцій, але в сучасному коді перевага надається async/await синтаксису з asyncio.Task.
У мобільній розробці Future активно використовується у Flutter-додатках. Кожен мережевий запит з пакета http або dio повертає Future. FutureBuilder — віджет, який приймає Future та snapshot стану: connectionDone, hasData, hasError. При отриманні даних snapshot.data містить результат Future. StreamBuilder — аналог для множинних значень. FutureBuilder позбавляє від ручного керування станом завантаження через setState.
Scala Future використовує ExecutionContext неявно — пул потоків, на якому виконується асинхронне завдання. Композиція через map, flatMap, filter працює завдяки тому, що Future — це монада. for-comprehension дозволяє послідовно комбінувати кілька Future без вкладеності: for { a <- futureA; b <- futureB } yield a + b. Future.sequence працює як Promise.all. Fallback при помилці реалізується через recover та recoverWith.
Future/Promise в C++ представлений класами std::future та std::promise з бібліотеки <future>. std::async запускає асинхронну функцію та повертає std::future. std::promise дозволяє вручну встановити значення через set_value() або помилку через set_exception(). На відміну від Dart та JavaScript, std::future.get() блокує потік до отримання результату — це важлива відмінність, яку потрібно враховувати при проєктуванні багатопотокових додатків на C++.
При проєктуванні API, що повертають Future, дотримуйтеся правила єдиного джерела істини: кожен асинхронний запит створюється один раз і кешується через Future.memoize. Якщо два компоненти запитують одні й ті ж дані, вони отримують один і той же Future — це запобігає дублюванню запитів. Для timeout використовуйте Future.timeout (Dart) або Promise.race з таймаутом, щоб гарантувати, що асинхронна операція завершиться в межах заданого інтервалу.
Retry-патерн у Future реалізується через рекурсивний ланцюжок: при помилці в catch викликається та ж асинхронна функція із затримкою. Exponential backoff збільшує інтервал між спробами: 1 сек — 2 сек — 4 сек — до max 30 сек. У Dart це інкапсулюється в extension на Future: future.retry(maxRetries: 3, delay: Duration(seconds: 1)). У Scala Future.zip об’єднує два незалежних результати в кортеж — альтернатива Promise.all для двох Future. Cancellation у стандартних Future не підтримується, але реалізується через CancellationToken або AbortController в JavaScript.
Часті запитання
Future — це read-only контейнер для читання асинхронного результату через then або await. Promise — write-only контракт, який приймає значення через resolve() та пов’язує його з Future. У Dart та JavaScript ці поняття об’єднані: Future/Promise — один клас. У Java та Scala вони розділені.
Future представляє єдиний асинхронний результат — він емітить рівно одне значення та завершується. Observable (Rx) емітить множинні значення в часі — це асинхронний стрім. Якщо потрібна одна HTTP-відповідь — використовуйте Future. Якщо потрібен потік змін БД — Observable або Stream.
async/await — синтаксичний цукор над Future/Promise. Ключове слово async позначає функцію як асинхронну — вона повертає Future. await призупиняє виконання до завершення Future, не блокуючи потік. У Dart, JavaScript, Python та C# async/await замінив ланцюжки then у більшості сценаріїв.
У Dart використовуйте .catchError() в кінці ланцюжка — він перехоплює винятки на будь-якому етапі. У JavaScript — .catch(). У Dart також доступний try/await/catch всередині async-функцій. Для відновлення після помилки використовуйте .catchError() з поверненням значення за замовчуванням або нового Future.
Promise.all приймає масив промісів та повертає один Promise, який розв—язується масивом результатів. Всі проміси запускаються паралельно — це прискорює виконання незалежних операцій (наприклад, завантаження кількох файлів). Якщо будь-який проміс відхилено — весь Promise.all відхиляється з тією ж помилкою.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також