LINUX.ORG.RU

Future и promise

 


0

2

Вопрос такой, концепция future мне хорошо понятна. Запускаю блок кода на потоке из отдельного пула. После окончания вызывается callback в котором можно получить результат или исключение.

А зачем нужны промисы? Когда их нужно использовать?


Это какая-то не та концепция future.

Ты запускаешь кусок кода в отдельном потоке. В любой момент, из другого треда (выполняющего свой Future), ты можешь запросить результат вычисления. Этот другой тред остановится и будет ждать, пока результат будет получен. Вот, что такое Future, в основном.

Promise — больше деталь реализации Future. Их использовать как правило не нужно.

Miguel ★★★★★
()

Ты неверно понимаешь future. Это объект, к которому можно обратится(в коде) когда он еще не сконфигурирован, и это связано не с отдельным потоком, а с асинхронностью

Промисом наверное можно называть то, что ты называешь фьючером, когда сам фьючер скрыт от программиста, но тот может присвоить ему колбэк.

logicoop1
()

промиз это разве не тот же футур, только в другой стороны? Или у скале все по другому?

anonymous
()
Ответ на: комментарий от logicoop1

Вот пример c асинхронным callback, тут future - явно инстанцируемая сущность и работает как я написал выше, а промис - отдельная сущность:

def grind(beans: CoffeeBeans): Future[GroundCoffee] = Future {

 println("start grinding...")

 Thread.sleep(Random.nextInt(2000))

 if (beans == "baked beans") throw GrindingException("are you joking?")

 println("finished grinding...")

 s"ground coffee of $beans"

 }

 grind("baked beans").onComplete {

 case Success(ground) =>

 println(s"got my $ground")

 case Failure(ex) =>

 println("This grinder needs a replacement, seriously!")

 }

Miha
() автор топика
Ответ на: комментарий от logicoop1

Например:

 
case class TaxCut(reduction: Int)

 val taxcut = Promise[TaxCut]()

 val taxcutF: Future[TaxCut] = taxcut.future

Я сейчас книжку про это читаю, но ясного понимания не возникает.

Miha
() автор топика
Ответ на: комментарий от Miha

По-ходу, каждый дезигнер недоязычка понимает эти термины в меру своей испорченности. Я считаю, что никакого промиса вообще нет, есть только фьючер, объект сконфигурированный на определенную реакцию от асинхронного сигнала. Это то же самое что «объект-обработчик».

Проблема в том, что всякие мудрецы пытаются сделать асинхронный код псевдолинейным(и это несмотря на то, что те же адепты орут больше всех о «вреде императивщины»), пытаются засунуть асинхронную обработку в функцию с линейным кодом, отсюда и возникают все эти аномалии

logicoop1
()
Ответ на: комментарий от logicoop1

на определенную реакцию от асинхронного сигнала

Это актор, отдельная сущность, которая многоразовая, в отличие от future или промиса, используемого ровно один раз с возвращением потока в пул.

Miha
() автор топика

Это одно и то же, просто с разным API.

Legioner ★★★★★
()

future - ссылка на результат, который либо уже доступен, либо ещё вычисляется, но будет доступен в будущем.

promise - механизм завершения future, фактически одна из реализаций, стандартная. Создаёшь promise, берёшь от него ссылку на future, передаёшь на выход... а в каком-нибудь callback завершаешь promise (сразу или спустя какое-то время, с результатом или исключением).

https://docs.scala-lang.org/overviews/core/futures.html тут же идеально всё расписано, ребёнок поймёт.

BattleCoder ★★★★★
()
Ответ на: комментарий от BattleCoder

promise - механизм завершения future

Вот и непонятно, зачем его завершать, если future завершается сама после последней инструкции в коде блока?

Miha
() автор топика
Ответ на: комментарий от Miha

В книге пишут, можно просто пользоваться future и про промисы даже не вспоминать. Но очень интересно, для каких целей они все-таки созданы?

Miha
() автор топика
Ответ на: комментарий от Miha

Вы не поняли. Это конструктор. Если вы создадите promise, потом возьмёте с него ссылку promise.future - этот future никогда не завершится, до тех пор пока кто-то не вызовет promise.success/failure/complete.

BattleCoder ★★★★★
()
Ответ на: комментарий от BattleCoder

этот future никогда не завершится, до тех пор пока кто-то не вызовет promise.success/failure/complete

Спасибо, это понятно. Но главный вопрос остается, зачем этот механизм нужен?

Miha
() автор топика
Ответ на: комментарий от Miha

Например, есть какое-то событие - вы не знаете, когда оно выполнится. Раз в секунду дёргаете какой-нибудь запрос (HTTP/SQL/что угодно) - и если он возвращает нужные данные - вызываете promise.success с нужным значением. А если нет, то в цикле пробуете ещё через секунду. Это называют ещё polling.

Или другой пример, подписываетесь на push-уведомления, или на очередь сообщений (JMS/AMQP) - аналогично, при получении нового сообщения вызываете promise, возвращаете результат.

BattleCoder ★★★★★
()
Ответ на: комментарий от BattleCoder

Где дергают запрос в promise.future?

promise.success с нужным значением

И дальше что? Кто читает значение?

Miha
() автор топика

Промисы связаны с концепцией stateful-фьючи таким образом, что промис - это реализация, а фьюча - это интерфейс. Стандартные фьючи в скале являются stateful, т.е. могут иметь состояние. Промис - это такое отложенное состояние.

Бывают еще stateless-фьючи. Вот, последние уже ближе к собственно монадам и ФП, но это не совсем про скалу.

dave ★★★★★
()

Future это просто контейнер для значения которое будет представлено в некотором будущем. Когда оно будет получено, с ним можно будет произвести какие-то операции вроде map, flatMap, onComplete и т д. Сам же результат формируется функциональным объектом называемым Promise. И если у Future существует конкретная реализация, то вот реализация Promise может быть совершенно любая. Она может принять анонимную функцию и выполнить ее на экзекуторе, а затем заполнить футуру (это дефолтные промисы scala). Может принять функцию но не выполнять ее до запроса со стороны кода. Или может вообще не принимать никакую функцию, а будет заполнять футуру по ручному запросу success со стороны прикладного кода (в js такое очень любят).

В общем вариантов очень много. Важно знать что Promise это некоторая функция, которая вырабатывает результат и кладет значение в контейнер Future, и после его получения, он может произвести с ним различные трансформации. При этом утверждение что Promise обязательно асинхронна неверно, так как это зависит только от ее конкретной реализации.

Это скажем так общая теория Future/Prmose из Computer Scince. В scala если исходники посмотреть немного смазанная реализация данной парадигмы, но принципы те же.

Serbis
()

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 ★★★★★
()
Последнее исправление: cdshines (всего исправлений: 2)

Промисы — это переходник между низкоуровневыми событиями/колбэками и композабельными фьючерами. С их помощью ожидание колбэка/события можно представить в виде future.

anonymous
()
Ответ на: комментарий от cdshines

Спасибо за развернутый ответ.

Miha
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.