История изменений
Исправление cdshines, (текущая версия) :
future - это просто «композабельный» способ выполнять какие-то действия асинхронно, ты запустил ее и потом, по-хорошему, даже не знаешь, когда оно закончится. Вместо этого комбинируешь результаты с помощью функций, то есть говоришь «когда результат доступен, сделай это», и так сколько хочешь.
promise - это более примитивный конструкт коммуникации, грубо говоря, одноразово присваемая переменная (которую можно либо не присвоить, либо присвоить значение, либо завершить с ошибкой). Из промиса фьюча получается вполне естественно, у него есть метод Promise#future, который вернет фьючу, которая будет завершена (с Success или Failure), как только снаружи ты (или кто-то другой) завершить промис.
Простейший пример _обоснованного_ использования - превращение callback-ориентированного АПИ в более идиоматичное, на фьючах. Представь, что у тебя есть какой-то код, который принимает коллбек:
// бд тормозит, форкнемся в другой тред
def query(id: Int, cb: Callback[Foo]): Unit = {
new Thread { () =>
try {
cb.onSuccess(db.lookup(id))
} catch {
case NonFatal(e) => cb.onFailure(e)
}
}.start()
}
// такая лапша получается:
query(
17,
new Callback() {
def onSuccess(foo: Foo) = {
anotherQuery(
foo,
new Callback() {
// ну ты понял
}
)
}
def onFailure(err) = logger.error("Failed to get resource with id = 17")
}
)
Вместо это делаешь так:
def query(x: Int): Future[Foo] = {
val p: Promise[Foo] = Promise() // пустой промис
new Thread { () =>
try p.success(db.lookup(x)) catch {
case NonFatal(e) => p.failure(e)
}
}.start()
p.future
}
def anotherQuery(foo: Foo): Future[Bar] = {
val p: Promise[Bar] = Promise() // пустой промис
new Thread { () =>
try p.success(db.lookup(foo)) catch {
case NonFatal(e) => p.failure(e)
}
}.start()
p.future
}
// и, наконец
val futureBar = for {
foo <- query(17)
bar <- query(foo)
} yield {
bar
}
// что то же самое, что и
val futureBar = query(17).flatMap { foo =>
anotherQuery(foo)
}
Этот пример, конечно, исключительно синтаксически мотивирован, то важно здесь то, что видно, как можно переходить от лапши на коллбеках к более композабельным компонентам.
Важно еще понимать, что future - не ссылочно-прозрачная, если тебе интересен более принципиальный подход, посмотри в сторону IO/Task (это pure FP-фьючи, грубо говоря), там есть Deferred, который является pure FP-промисом:
- https://typelevel.org/cats-effect/datatypes/io.html
- https://typelevel.org/cats-effect/concurrency/deferred.html
Исправление cdshines, :
future - это просто «композабельный» способ выполнять какие-то действия асинхронно, ты запустил ее и потом, по-хорошему, даже не знаешь, когда оно закончится. Вместо этого комбинируешь результаты с помощью функций, то есть говоришь «когда результат доступен, сделай это», и так сколько хочешь.
promise - это более примитивный конструкт коммуникации, грубо говоря, одноразово присваемая переменная (которую можно либо не присвоить, либо присвоить значение, либо завершить с ошибкой). Из промиса фьюча получается вполне естественно, у него есть метод Promise#future, который вернет фьючу, которая будет завершена (с Success или Failure), как только снаружи ты (или кто-то другой) завершить промис.
Простейший пример _обоснованного_ использования - превращение callback-ориентированного АПИ в более идиоматичное, на фьючах. Представь, что у тебя есть какой-то код, который принимает коллбек:
// бд тормозит, форкнемся в другой тред
def query(id: Int, cb: Callback[Foo]): Unit = {
new Thread { () =>
try {
cb.onSuccess(db.lookup(id))
} catch {
case NonFatal(e) => cb.onFailure(e)
}
}.start()
}
// такая лапша получается:
query(
17,
new Callback() {
def onSuccess(foo: Foo) = {
anotherQuery(
foo,
new Callback() {
// ну ты понял
}
)
}
def onFailure(err) = logger.error("Failed to get resource with id = 17")
}
)
Вместо это делаешь так:
def query(x: Int): Future[Foo] = {
val p: Promise[Foo] = Promise() // пустой промис
new Thread { () =>
try p.success(db.lookup(x)) catch {
case NonFatal(e) => p.failure(e)
}
}.start()
p.future
}
def anotherQuery(foo: Foo): Future[Bar] = {
val p: Promise[Bar] = Promise() // пустой промис
new Thread { () =>
try p.success(db.lookup(foo)) catch {
case NonFatal(e) => p.failure(e)
}
}.start()
p.future
}
// и, наконец
val futureBar = for {
foo <- query(17)
bar <- query(foo)
} yield {
bar
}
// что то же самое, что и
val futureBar = query(17).flatMap { foo =>
anotherQuery(foo)
}
Этот пример, конечно, исключительно синтаксически мотивирован, то важно здесь то, что видно, как можно переходить от лапши на коллбеках к более композабельным компонентам.
Важно еще понимать, что future - не ссылочно-прозрачная, если тебе интересен более принципиальный подход, посмотри в сторону IO/Task (это pure FP-фьючи, грубо говоря), там есть Deferred, который является pure FP-промисом:
- https://typelevel.org/cats-effect/datatypes/io.html - https://typelevel.org/cats-effect/concurrency/deferred.html
Исходная версия cdshines, :
future - это просто «композабельный» способ выполнять какие-то действия асинхронно, ты запустил ее и потом, по-хорошему, даже не знаешь, когда оно закончится. Вместо этого комбинируешь результаты с помощью функций, то есть говоришь «когда результат доступен, сделай это», и так сколько хочешь.
promise - это более примитивный конструкт коммуникации, грубо говоря, одноразово присваемая переменная (которую можно либо не присвоить, либо присвоить значение, либо завершить с ошибкой). Из промиса фьюча получается вполне естественно, у него есть метод Promise#future, который вернет фьючу, которая будет завершена (с Success или Failure), как только снаружи ты (или кто-то другой) завершить промис.
Простейший пример _обоснованного_ использования - превращение callback-ориентированного АПИ в более идиоматичное, на фьючах. Представь, что у тебя есть какой-то код, который принимает коллбек:
// бд тормозит, форкнемся в другой тред
def query(id: Int, cb: Callback[Foo]): Unit = {
new Thread { () =>
try {
cb.onSuccess(db.lookup(id))
} catch {
case NonFatal(e) => cb.onFailure(e)
}
}.start()
}
// такая лапша получается:
query(
17,
new Callback() {
def onSuccess(foo: Foo) = {
anotherQuery(
foo,
new Callback() {
// ну ты понял
}
)
}
def onFailure(err) = logger.error("Failed to get resource with id = 17")
}
)
Вместо это делаешь так:
def query(x: Int): Future[Foo] = {
val p: Promise[Foo] = Promise() // пустой промис
new Thread { () =>
try p.success(db.lookup(x)) catch {
case NonFatal(e) => p.failure(e)
}
}.start()
p.future
}
def anotherQuery(foo: Foo): Future[Bar] = {
val p: Promise[Bar] = Promise() // пустой промис
new Thread { () =>
try p.success(db.lookup(foo)) catch {
case NonFatal(e) => p.failure(e)
}
}.start()
p.future
}
// и, наконец
val futureBar = for {
foo <- query(17)
bar <- query(foo)
} yield {
bar
}
// что то же самое, что и
val futureBar = query(17).flatMap { foo =>
anotherQuery(foo)
}
Этот пример, конечно, исключительно синтаксически мотивирован, то важно здесь то, что видно, как можно переходить от лапши на коллбеках к более композабельным компонентам.