LINUX.ORG.RU

История изменений

Исправление 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)
}

Этот пример, конечно, исключительно синтаксически мотивирован, то важно здесь то, что видно, как можно переходить от лапши на коллбеках к более композабельным компонентам.