Obietnica i przyszłość to koncepcje uzupełniające się. Przyszłość to wartość, która zostanie odzyskana, no cóż, kiedyś w przyszłości i możesz z nią coś zrobić, gdy nastąpi to wydarzenie. Jest to zatem punkt końcowy odczytu lub wyjścia obliczenia - jest to coś, z czego pobierasz wartość.
Obietnica jest analogicznie stroną obliczeń do pisania. Tworzysz obietnicę, która jest miejscem, w którym umieścisz wynik obliczeń i z tej obietnicy otrzymasz przyszłość, która zostanie wykorzystana do odczytania wyniku, który został umieszczony w obietnicy. Kiedy wypełnisz obietnicę, albo przez porażkę, albo sukces, wywołasz wszystkie zachowania, które były związane z powiązaną z nią Przyszłością.
Odnosząc się do twojego pierwszego pytania, jak to możliwe, że mamy obietnicę p p.future == p
. Możesz to sobie wyobrazić jako bufor pojedynczego elementu - kontener, który jest początkowo pusty i możesz później przechowywać jedną wartość, która na zawsze stanie się jego zawartością. Teraz, w zależności od twojego punktu widzenia, jest to zarówno obietnica, jak i przyszłość. To obietnica dla kogoś, kto zamierza zapisać wartość w buforze. To przyszłość dla kogoś, kto czeka, aż ta wartość zostanie umieszczona w buforze.
W szczególności w przypadku współbieżnego interfejsu API Scala, jeśli spojrzysz na cechę Promise w tym miejscu , możesz zobaczyć, jak są implementowane metody z obiektu towarzyszącego Promise:
object Promise {
def apply[T](): Promise[T] = new impl.Promise.DefaultPromise[T]()
def failed[T](exception: Throwable): Promise[T] = new impl.Promise.KeptPromise[T](Failure(exception))
def successful[T](result: T): Promise[T] = new impl.Promise.KeptPromise[T](Success(result))
}
Teraz te implementacje obietnic, DefaultPromise i KeptPromise można znaleźć tutaj . Oba rozszerzają podstawową małą cechę, która ma tę samą nazwę, ale znajduje się w innym opakowaniu:
private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with scala.concurrent.Future[T] {
def future: this.type = this
}
Możesz więc zobaczyć, co mają na myśli p.future == p
.
DefaultPromise
jest buforem, o którym mówiłem powyżej, podczas gdy KeptPromise
jest buforem z wartością wprowadzoną od samego jego utworzenia.
Jeśli chodzi o twój przykład, przyszły blok, którego tam używasz, faktycznie tworzy obietnicę za kulisami. Spójrzmy na definicję future
w tutaj :
def future[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = Future[T](body)
Postępując zgodnie z łańcuchem metod, znajdziesz się w impl.Future :
private[concurrent] object Future {
class PromiseCompletingRunnable[T](body: => T) extends Runnable {
val promise = new Promise.DefaultPromise[T]()
override def run() = {
promise complete {
try Success(body) catch { case NonFatal(e) => Failure(e) }
}
}
}
def apply[T](body: =>T)(implicit executor: ExecutionContext): scala.concurrent.Future[T] = {
val runnable = new PromiseCompletingRunnable(body)
executor.execute(runnable)
runnable.promise.future
}
}
Tak więc, jak widać, wynik uzyskany z bloku producenta zamienia się w obietnicę.
PÓŹNIEJSZA EDYCJA :
Odnośnie zastosowania w świecie rzeczywistym: w większości przypadków nie będziesz zajmować się bezpośrednio obietnicami. Jeśli użyjesz biblioteki, która wykonuje obliczenia asynchroniczne, będziesz po prostu pracować z futures zwracanymi przez metody biblioteki. W tym przypadku obietnice są tworzone przez bibliotekę - po prostu pracujesz nad czytaniem tego, co robią te metody.
Ale jeśli chcesz zaimplementować własne asynchroniczne API, musisz zacząć z nimi pracować. Załóżmy, że musisz zaimplementować asynchronicznego klienta HTTP oprócz, powiedzmy, Netty. Wtedy twój kod będzie wyglądał mniej więcej tak
def makeHTTPCall(request: Request): Future[Response] = {
val p = Promise[Response]
registerOnCompleteCallback(buffer => {
val response = makeResponse(buffer)
p success response
})
p.future
}