Jaka jest różnica między przyszłością a obietnicą?

274

Jaka jest różnica między Futurei Promise?
Oba działają jak symbol zastępczy dla przyszłych wyników, ale gdzie jest główna różnica?

użytkownik1170330
źródło
99
Możesz zrobić Promisei to zależy od ciebie, aby go zatrzymać. Gdy ktoś inny złoży ci obietnicę, musisz poczekać, aby zobaczyć, czy ją dotrzymaFuture
Kevin Wright
1
wikipedia Kontrakty terminowe i obietnice
pisarz
30
Jeden z najmniej pomocnych artykułów Wikipedii, jakie kiedykolwiek czytałem
Fulluphigh

Odpowiedzi:

145

Według tej dyskusji , Promisew końcu został powołany CompletableFuturedo włączenia w Java 8, a jego javadoc wyjaśnia:

Przyszłość, która może zostać jawnie ukończona (ustawienie jej wartości i statusu) i może być wykorzystana jako CompletionStage, obsługująca zależne funkcje i akcje uruchamiane po jej zakończeniu.

Przykład podano również na liście:

f.then((s -> aStringFunction(s)).thenAsync(s -> ...);

Pamiętaj, że końcowy interfejs API jest nieco inny, ale umożliwia podobne wykonywanie asynchroniczne:

CompletableFuture<String> f = ...;
f.thenApply(this::modifyString).thenAccept(System.out::println);
assylias
źródło
78
To nie twoja wina Assylias, ale ten wyciąg z javadoc wymaga poważnego przerobienia przez porządnego autora technologii. Po piątym przeczytaniu mogę zacząć doceniać to, co próbuje powiedzieć ... i dochodzę do tego ze zrozumieniem przyszłości i obietnic, które już istnieją!
Beetroot-Beetroot
2
@ Beetroot-Beetroot wygląda na to, że stało się już teraz.
herman
1
@herman Thanks - Zaktualizowałem link, aby wskazywał na ostateczną wersję javadoc.
assylias
7
@ Beetroot-Beetroot Powinieneś sprawdzić dokument dla metody Wyjątkowo. Byłby to wspaniały wiersz, ale jest wyjątkową porażką czytelnej dokumentacji.
Fulluphigh,
4
Dla każdego, kto się zastanawia, @Fulluphigh odnosi się do tego . Wygląda na to, że został usunięty / zmieniony w Javie 8.
Cedric Reichenbach,
148

(Jak dotąd nie jestem do końca zadowolony z odpowiedzi, więc oto moja próba ...)

Myślę, że komentarz Kevina Wrighta ( „Możesz złożyć obietnicę, a dotrzymanie jej zależy od ciebie. Gdy ktoś inny złoży ci obietnicę, musisz poczekać, czy ją dotrzyma w przyszłości” ) dość dobrze ją podsumowuje, ale niektóre wyjaśnienie może być przydatne.

Futures i obietnice są dość podobnymi koncepcjami, różnica polega na tym, że przyszłość jest pojemnikiem tylko do odczytu dla wyniku, który jeszcze nie istnieje, podczas gdy obietnica może być napisana (zwykle tylko raz). Java 8 CompletableFuture i Guava SettableFuture można traktować jako obietnice, ponieważ ich wartość można ustawić („ukończono”), ale implementują one również interfejs Future, dlatego nie ma różnicy dla klienta.

Wynik przyszłości zostanie ustalony przez „kogoś innego” - na podstawie obliczeń asynchronicznych. Zwróć uwagę, że FutureTask - klasyczna przyszłość - musi zostać zainicjalizowany za pomocą Callable lub Runnable, nie ma konstruktora bez argumentów, a Future i FutureTask są tylko do odczytu z zewnątrz (ustawione metody FutureTask są chronione). Wartość zostanie ustawiona na wynik obliczeń od wewnątrz.

Z drugiej strony, wynik obietnicy może być ustawiony przez „ciebie” (a właściwie przez kogokolwiek) w dowolnym momencie, ponieważ ma on metodę publicznego ustawiania. Zarówno CompletableFuture, jak i SettableFuture można tworzyć bez żadnego zadania, a ich wartość można ustawić w dowolnym momencie. Wysyłasz obietnicę do kodu klienta i wypełniasz ją później, jak chcesz.

Należy pamiętać, że CompletableFuture nie jest „czystą” obietnicą, można ją zainicjować za pomocą zadania takiego jak FutureTask, a jej najbardziej użyteczną funkcją jest niepowiązane tworzenie łańcuchów etapów przetwarzania.

Zauważ też, że obietnica nie musi być podtypem przyszłości i nie musi być tym samym przedmiotem. W Scali przyszły obiekt jest tworzony przez obliczenia asynchroniczne lub inny obiekt Promise. W C ++ sytuacja jest podobna: obiekt przyrzeczenia jest używany przez producenta, a obiekt przyszły przez konsumenta. Zaletą tego rozdzielenia jest to, że klient nie może ustalić wartości przyszłości.

Zarówno Spring, jak i EJB 3.1 mają klasę AsyncResult, która jest podobna do obietnic Scala / C ++. AsyncResult implementuje Future, ale to nie jest prawdziwa przyszłość: metody asynchroniczne w Spring / EJB zwracają inny obiekt Future tylko do odczytu poprzez magię tła, a ta druga „prawdziwa” przyszłość może zostać wykorzystana przez klienta w celu uzyskania dostępu do wyniku.

lbalazscs
źródło
116

Wiem, że odpowiedź jest już zaakceptowana, ale mimo to chciałbym dodać dwa centy:

TLDR: Przyszłość i obietnica to dwie strony działania asynchronicznego: konsument / rozmówca vs. producent / realizator .

Jako wywołujący metodę asynchronicznego interfejsu API otrzymasz Futureuchwyt do wyniku obliczeń. Możesz np. Zadzwonić get(), aby poczekać na zakończenie obliczeń i pobrać wynik.

Pomyśl teraz o tym, jak ta metoda API jest faktycznie zaimplementowana: implementator musi Futurenatychmiast zwrócić . Są odpowiedzialni za ukończenie tej przyszłości, jak tylko obliczenia zostaną wykonane (co będą wiedzieć, ponieważ implementuje logikę wysyłki ;-)). Użyją Promise/, CompletableFutureaby to zrobić: Skonstruuj i zwróć CompletableFuturenatychmiast, i zadzwoń complete(T result)po zakończeniu obliczeń.

Rahel Lüthy
źródło
1
Czy to oznacza, że ​​obietnica jest zawsze podklasą przyszłości i że typ zapisu przyszłości jest po prostu zasłonięty?
devios1
Nie sądzę, że jest to dorozumiane . Jeśli chodzi o implementację, często tak będzie (np. W Javie, Scali).
Rahel Lüthy,
74

Podam przykład tego, czym jest Obietnica i jak można ustawić jej wartość w dowolnym momencie, w przeciwieństwie do Przyszłości, której wartość można odczytać.

Załóżmy, że masz mamę i prosisz ją o pieniądze.

// Now , you trick your mom into creating you a promise of eventual
// donation, she gives you that promise object, but she is not really
// in rush to fulfill it yet:
Supplier<Integer> momsPurse = ()-> {

        try {
            Thread.sleep(1000);//mom is busy
        } catch (InterruptedException e) {
            ;
        }

        return 100;

    };


ExecutorService ex = Executors.newFixedThreadPool(10);

CompletableFuture<Integer> promise =  
CompletableFuture.supplyAsync(momsPurse, ex);

// You are happy, you run to thank you your mom:
promise.thenAccept(u->System.out.println("Thank you mom for $" + u ));

// But your father interferes and generally aborts mom's plans and 
// completes the promise (sets its value!) with far lesser contribution,
// as fathers do, very resolutely, while mom is slowly opening her purse 
// (remember the Thread.sleep(...)) :
promise.complete(10); 

Wynikiem tego jest:

Thank you mom for $10

Obietnica mamy została stworzona, ale czekała na jakieś „zakończenie”.

CompletableFuture<Integer> promise...

Stworzyłeś takie wydarzenie, akceptując jej obietnicę i ogłaszając plany podziękowania mamie:

promise.thenAccept...

W tym momencie mama zaczęła otwierać torebkę ... ale bardzo powoli ...

a ojciec interweniował znacznie szybciej i wypełnił obietnicę zamiast twojej mamy:

promise.complete(10);

Czy zauważyłeś wykonawcę, który napisałem wyraźnie?

Co ciekawe, jeśli zamiast tego użyjesz domyślnego domyślnego modułu wykonującego (commonPool), a ojca nie ma w domu, ale tylko mama z jej „powolną torebką”, wówczas jej obietnica się spełni, jeśli program żyje dłużej niż mama potrzebuje pieniędzy z portmonetka.

Domyślny moduł wykonujący działa jak „demon” i nie czeka na spełnienie wszystkich obietnic. Nie znalazłem dobrego opisu tego faktu ...

Vladimir Nabokov
źródło
8
Fajnie jest czytać ten! Nie sądzę, żebym mógł już zapomnieć o przyszłości i obiecać.
user1532146,
2
To musi zostać zaakceptowane jako odpowiedź. To jest jak czytanie historii. Dzięki @Vladimir
Phillen
Dzięki @Vladimir
intvprep
9

Nie jestem pewien, czy może to być odpowiedź, ale jak widzę, co inni powiedzieli dla kogoś, może to wyglądać tak, jakbyś potrzebował dwóch oddzielnych abstrakcji dla obu tych pojęć, tak aby jedna z nich ( Future) była tylko widokiem do odczytu drugiej ( Promise) ... ale tak naprawdę nie jest to potrzebne.

Na przykład spójrz, jak obietnice są zdefiniowane w javascript:

https://promisesaplus.com/

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Nacisk kładziony jest na kompozycję przy użyciu thenmetody takiej jak:

asyncOp1()
.then(function(op1Result){
  // do something
  return asyncOp2();
})
.then(function(op2Result){
  // do something more
  return asyncOp3();
})
.then(function(op3Result){
  // do something even more
  return syncOp4(op3Result);
})
...
.then(function(result){
  console.log(result);
})
.catch(function(error){
  console.log(error);
})

co sprawia, że ​​obliczenia asynchroniczne wyglądają jak synchroniczne:

try {
  op1Result = syncOp1();
  // do something
  op1Result = syncOp2();
  // do something more
  op3Result = syncOp3();
  // do something even more
  syncOp4(op3Result);
  ...
  console.log(result);
} catch(error) {
  console.log(error);
}

co jest całkiem fajne. (Nie tak fajne, jak async-czekaj, ale async-czekaj po prostu usuwa płytkę kotła .... wtedy (funkcja (wynik) {.... z niej).

W rzeczywistości ich abstrakcja jest całkiem dobra jako konstruktor obietnic

new Promise( function(resolve, reject) { /* do it */ } );

pozwala podać dwa oddzwaniania, których można użyć do Promisepomyślnego ukończenia lub z błędem. Tak więc tylko kod, który konstruuje, Promisemoże go ukończyć, a kod, który odbiera już skonstruowany Promiseobiekt, ma widok tylko do odczytu.

Dzięki dziedziczeniu powyższe można osiągnąć, jeśli rozwiązania i odrzucenia są chronionymi metodami.

bodrin
źródło
4
+1. To jest poprawna odpowiedź na to pytanie. CompletableFuturemoże mieć pewne podobieństwo do Promiseale ,Promise ale nadal tak nie jest , ponieważ sposób, w jaki ma być spożywany, jest inny: Promisewynik jest zużywany przez wywołanie then(function), a funkcja jest wykonywana w kontekście producenta natychmiast po wywołaniu przez producenta resolve. FutureWynik jest zużywana jest przez wywołanie getco powoduje nić konsumentów czekać aż gwint producent wygenerował wartość, a następnie przetwarza je na konsumenta. Futurejest z natury wielowątkowy, ale ...
Periata Breatta
5
... jest całkowicie możliwe użycie Promisetylko jednego wątku (w rzeczywistości jest to dokładne środowisko, dla którego zostały pierwotnie zaprojektowane: aplikacje javascript mają zazwyczaj tylko jeden wątek, więc nie możnaFuture tam zaimplementować ). Promisejest zatem znacznie lżejszy i wydajniejszy niż Future, ale Futuremoże być pomocny w sytuacjach, które są bardziej złożone i wymagają współpracy między wątkami, których nie można łatwo za pomocą Promises. Podsumowując: Promisejest modelem push, podczas gdy Futurejest modelem pull (por. Iterable vs Observable)
Periata Breatta
@PeriataBreatta Nawet w środowisku jednowątkowym musi być coś spełniającego obietnicę (która zazwyczaj działa jako inny wątek, np. An XMLHttpRequest). Nie wierzę w deklarację efektywności, czy zdarza ci się mieć jakieś liczby? +++ To powiedziawszy, bardzo miłe wytłumaczenie.
maaartinus,
1
@maaartinus - tak, coś musi spełnić obietnicę, ale można to (i w wielu przypadkach tak się dzieje) za pomocą pętli najwyższego poziomu, która wyszukuje zmiany stanu zewnętrznego i rozwiązuje dowolne obietnice dotyczące zakończonych działań. Jeśli chodzi o efektywność, nie mam konkretnych danych na temat obietnic, ale zauważ, że wezwanie getdo nierozwiązania Futurebędzie wymagało 2 przełączników kontekstowych wątków, co przynajmniej kilka lat temu prawdopodobnie wymagałoby około 50 nas .
Periata Breatta
@PeriataBreatta Właściwie twój komentarz powinien być zaakceptowanym rozwiązaniem. Szukałem takiego wyjaśnienia (pull / push, single / multi-thread).
Thomas Jacob
5

W przypadku kodu klienta Promise służy do obserwowania lub dołączania oddzwaniania, gdy wynik jest dostępny, natomiast Future ma czekać na wynik, a następnie kontynuować. Teoretycznie wszystko, co można zrobić z przyszłością, co można zrobić z obietnicami, ale ze względu na różnicę stylu wynikowe API dla obietnic w różnych językach ułatwia tworzenie łańcuchów.

użytkownik2562234
źródło
2

Brak metody ustawionej w interfejsie Future, tylko metoda get, więc jest tylko do odczytu. O CompletableFuture ten artykuł może być pomocny. pełna przyszłość

Jacky
źródło