Future/Promise to wzorzec programowania asynchronicznego, reprezentujący kontener na wynik jeszcze niezakończonej operacji. Według Dart Documentation, 2025, Future to read-only wynik odroczonych obliczeń, Promise to write-only kontrakt, który ten wynik wypełnia. Future jest używany w Dart, Java, Scala i C++ dla łańcuchów then/catch.
Najważniejsze
Future/Promise to para powiązanych obiektów, które dzielą wynik asynchroniczny na dwie role. Promise może zapisać wynik (resolve) lub błąd (reject). Future może go tylko odczytać — przez callback (then, catchError) lub przez await. Taki podział gwarantuje, że konsument nie może zmienić wyniku, a jedynie go użyć.
Język Dart używa Future jako głównego mechanizmu asynchroniczności. Future<T> to kontrakt na otrzymanie wartości typu T w przyszłości. Future może być w jednym z trzech stanów: unresolved (operacja jest wykonywana), resolved with value (zakończona pomyślnie) lub resolved with error (zakończona błędem). Po przejściu w stan resolved Future nie zmienia się.
Promise w JavaScript to odpowiednik Future z Darta, ale z bogatszym API. Promise przyjmuje executor — funkcję z parametrami resolve i reject. Statyczne metody Promise.all, Promise.race, Promise.allSettled pozwalają łączyć wiele promiseów. Nowoczesne aplikacje używają składni async/await opartej na Promise.
Future działa w oparciu o event loop — pętlę zdarzeń, która przetwarza operacje asynchroniczne bez blokowania głównego wątku. Kiedy wywoływana jest funkcja asynchroniczna (np. http.get), natychmiast zwraca Future. Rzeczywiste żądanie jest wykonywane w tle, a event loop kontynuuje przetwarzanie innych zdarzeń. Po zakończeniu operacji Future przechodzi w stan resolved i wywoływany jest then.
Event loop to kluczowy mechanizm, na którym zbudowane są Dart i JavaScript. Event loop utrzymuje dwie kolejki: microtask queue (krótkie zadania wykonywane przed następnym zdarzeniem) i event queue (zdarzenia IO, timery). Future.then umieszcza callback w microtask queue, Future.microtask — priorytetowe wykonanie przed następną klatką.
Łańcuchy then() pozwalają na sekwencyjne przetwarzanie wyników operacji asynchronicznych bez callback hell. Każdy then() otrzymuje wynik poprzedniego kroku i zwraca nowy Future. catchError() na końcu łańcucha obsługuje dowolny błąd, który wystąpił na którymkolwiek etapie. To sprawia, że kod asynchroniczny staje się liniowy i czytelny.
W Java CompletableFuture rozszerza koncepcję Future/Promise. Metoda supplyAsync() uruchamia zadanie asynchroniczne w ForkJoinPool.commonPool(). thenApply() transformuje wynik, thenCompose() — rozwija zagnieżdżony CompletableFuture. exceptionally() obsługuje błąd, zwracając wartość domyślną. allOf() i anyOf() działają jak Promise.all i Promise.race w JavaScript. CompletableFuture obsługuje ręczne zakończenie przez complete() i completeExceptionally() — to odpowiednik Promise resolve/reject.
Promise to kontrakt z jednym z trzech stanów: pending (oczekiwanie), fulfilled (zakończony pomyślnie) lub rejected (zakończony błędem). Po przejściu w stan fulfilled lub rejected Promise nigdy się nie zmienia — gwarantuje to niezmienność wyniku dla wszystkich konsumentów. Metoda .then() jest wywoływana asynchronicznie po przejściu w dowolny stan zakończony.
Promise.all oczekuje na zakończenie wszystkich przekazanych promiseów i zwraca tablicę wyników w tej samej kolejności. Jeśli jeden promise zakończył się błędem — cały Promise.all przechodzi w stan rejected. Promise.race zwraca wynik pierwszego zakończonego promisea (sukces lub błąd). Promise.allSettled czeka na wszystkie promisey niezależnie od błędów — zwraca status każdego.
Chaining — kluczowa zaleta Promise w porównaniu z callback. Każdy .then() zwraca nowy Promise, pozwalając na budowanie łańcuchów o dowolnej długości. Błąd w dowolnym ogniwie łańcucha jest propagowany do najbliższego .catch(). Metoda finally() wykonuje kod po zakończeniu łańcucha niezależnie od wyniku — do czyszczenia zasobów.
Pierwszy przykład — tworzenie Future przez async-funkcję i obsługa przez then/catchError:
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 1))
return "Dane załadowane"
}
fetchData()
.then((result) => print(result))
.catchError((error) => print("Błąd: $error"))
Drugi przykład — łączenie wielu Future przez Future.wait:
Future<List<String>> loadAll() async {
final futures = [
fetchFromApi("/users"),
fetchFromApi("/posts"),
fetchFromApi("/comments")
]
final results = await Future.wait(futures)
return results
}
Trzeci przykład — Promise w JavaScript z ręcznym tworzeniem i łańcuchem then:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Gotowe")
}, 1000)
})
promise
.then(value => console.log(value))
.catch(err => console.error(err))
Future/Promise różni się od stylu callback tym, że zwraca obiekt-kontrakt zamiast przyjmować funkcję zwrotną jako argument. Styl callback prowadzi do zagnieżdżenia (callback hell), podczas gdy Future pozwala na budowanie płaskich łańcuchów then(). Składnia async/await sprawia, że kod z Future jest jeszcze bardziej liniowy, przypominający synchroniczny.
Future reprezentuje dokładnie jeden wynik asynchroniczny — jedna emisja, po której Future się kończy. W przeciwieństwie do Observable (Rx, ReactiveX) lub Flow (Kotlin Coroutines), Future nie obsługuje wielokrotnych wartości w czasie. Jeśli potrzebny jest strumień zdarzeń — użyj Stream w Dart, Observable w Rx lub Flow w Kotlin.
CompletableFuture w Java — odpowiednik Promise z rozszerzonym API: supplyAsync, thenApply, thenCompose, exceptionally. CompletableFuture pozwala łączyć wiele operacji asynchronicznych przez allOf, anyOf, a także ustawić własny Executor do wykonania. Scala Future używa ExecutionContext do zarządzania pulą wątków i obsługuje kompozycję przez for-comprehension.
W Dart wzorzec Future różni się od JavaScript Promise tym, że Dart ma ścisłe typowanie: Future<String> gwarantuje typ zwracanej wartości. Funkcje async w Dart muszą zwracać Future, a kompilator sprawdza, czy wszystkie ścieżki zwracają poprawny typ. Future.wait jest odpowiednikiem Promise.all — przyjmuje Iterable<Future<T>> i zwraca Future<List<T>>. Future.delayed i Future.error to wygodne fabryki do testowania scenariuszy asynchronicznych.
Future/Promise jest zaimplementowany w Python przez concurrent.futures.Future i asyncio.Future. concurrent.futures.ThreadPoolExecutor.submit() zwraca Future, na którym można wywołać result() — wywołanie blokujące. asyncio.Future to await-kompatybilny kontener na wynik asynchroniczny w Asyncio. asyncio.gather() równolegle uruchamia kilka korutyn i zwraca listę wyników, podobnie jak Promise.all w JavaScript. Python używa również callback-based asyncio.ensure_future i łańcuchów add_done_callback do kompozycji operacji asynchronicznych, ale w nowoczesnym kodzie preferowana jest składnia async/await z asyncio.Task.
W programowaniu mobilnym Future jest aktywnie używany w aplikacjach Flutter. Każde żądanie sieciowe z pakietu http lub dio zwraca Future. FutureBuilder — widget, który przyjmuje Future i snapshot stanu: connectionDone, hasData, hasError. Po otrzymaniu danych snapshot.data zawiera wynik Future. StreamBuilder — odpowiednik dla wielokrotnych wartości. FutureBuilder eliminuje ręczne zarządzanie stanem ładowania przez setState.
Scala Future używa ExecutionContext niejawnie — puli wątków, na której wykonywane jest zadanie asynchroniczne. Kompozycja przez map, flatMap, filter działa dzięki temu, że Future to monada. for-comprehension pozwala na sekwencyjne łączenie wielu Future bez zagnieżdżenia: for { a <- futureA; b <- futureB } yield a + b. Future.sequence działa jak Promise.all. Obsługa błędu przy fallback realizowana jest przez recover i recoverWith.
Future/Promise w C++ jest reprezentowany przez klasę std::future i std::promise z biblioteki <future>. std::async uruchamia funkcję asynchroniczną i zwraca std::future. std::promise pozwala ręcznie ustawić wartość przez set_value() lub błąd przez set_exception(). W przeciwieństwie do Dart i JavaScript, std::future.get() blokuje wątek do momentu otrzymania wyniku — to ważna różnica, którą należy uwzględnić przy projektowaniu aplikacji wielowątkowych w C++.
Przy projektowaniu API zwracających Future, przestrzegaj zasady jedynego źródła prawdy: każde żądanie asynchroniczne jest tworzone raz i cacheowane przez Future.memoize. Jeśli dwa komponenty żądają tych samych danych, otrzymują ten sam Future — zapobiega to duplikowaniu żądań. Dla timeoutu użyj Future.timeout (Dart) lub Promise.race z timeoutem, aby zagwarantować, że operacja asynchroniczna zakończy się w określonym przedziale czasu.
Wzorzec Retry w Future jest realizowany przez rekurencyjny łańcuch: przy błędzie w catch wywoływana jest ta sama funkcja asynchroniczna z opóźnieniem. Exponential backoff zwiększa odstęp między próbami: 1 sek — 2 sek — 4 sek — do max 30 sek. W Dart jest to hermetyzowane w extension na Future: future.retry(maxRetries: 3, delay: Duration(seconds: 1)). W Scala Future.zip łączy dwa niezależne wyniki w krotkę — alternatywa dla Promise.all dla dwóch Future. Cancellation w standardowych Future nie jest obsługiwana, ale jest realizowana przez CancellationToken lub AbortController w JavaScript.
Często zadawane pytania
Future to read-only kontener do odczytu wyniku asynchronicznego przez then lub await. Promise to write-only kontrakt, który przyjmuje wartość przez resolve() i wiąże ją z Future. W Dart i JavaScript te pojęcia są połączone: Future/Promise to jedna klasa. W Java i Scala są rozdzielone.
Future reprezentuje pojedynczy wynik asynchroniczny — emituje dokładnie jedną wartość i kończy się. Observable (Rx) emituje wielokrotne wartości w czasie — to strumień asynchroniczny. Jeśli potrzebujesz jednej odpowiedzi HTTP — użyj Future. Jeśli potrzebujesz strumienia zmian bazy danych — użyj Observable lub Stream.
async/await to lukier składniowy nad Future/Promise. Słowo kluczowe async oznacza funkcję jako asynchroniczną — zwraca ona Future. await wstrzymuje wykonanie do czasu zakończenia Future, nie blokując wątku. W Dart, JavaScript, Python i C# async/await zastąpił łańcuchy then w większości scenariuszy.
W Dart użyj .catchError() na końcu łańcucha — przechwytuje on wyjątki na każdym etapie. W JavaScript — .catch(). W Dart dostępny jest również try/await/catch wewnątrz funkcji async. Do odzyskiwania po błędzie użyj .catchError() z zwróceniem wartości domyślnej lub nowego Future.
Promise.all przyjmuje tablicę promiseów i zwraca jeden Promise, który rozwiązuje się tablicą wyników. Wszystkie promisy uruchamiane są równolegle — przyspiesza to wykonywanie niezależnych operacji (np. ładowanie wielu plików). Jeśli którykolwiek promise zostanie odrzucony — cały Promise.all jest odrzucany z tym samym błędem.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również