Каковы варианты использования scala.concurrent.Promise?

Я читаю SIP-14, и концепция Future имеет смысл и легко понять. Но есть два вопроса о Promise:

  • SIP говорит Depending on the implementation, it may be the case that p.future == p. Как это может быть? Существуют ли Future и Promise не два разных типа?

  • Когда мы должны использовать Promise? Пример producer and consumer code:

    import scala.concurrent.{ future, promise }
    val p = promise[T]
    val f = p.future
    
    val producer = future {
        val r = produceSomething()
        p success r
        continueDoingSomethingUnrelated()
    }
    val consumer = future {
        startDoingSomething()
        f onSuccess {
            case r => doSomethingWithResult()
        }
    }
    

легко читается, но действительно ли нам нужно писать так? Я попытался реализовать его только с помощью Future и без Promise следующим образом:

val f = future {
   produceSomething()
}

val producer = future {
   continueDoingSomethingUnrelated()
}

startDoingSomething()

val consumer = future {
  f onSuccess {
    case r => doSomethingWithResult()
  }
}

В чем разница между этим и данным примером и что делает обещание необходимым?

Ответ 1

"Обещание и будущее" - это взаимодополняющие концепции. Будущее - это ценность, которая будет получена, ну, когда-нибудь в будущем, и вы сможете делать что-то с ней, когда это событие произойдет. Это, следовательно, конечная точка чтения или вывода вычислений - это то, что вы извлекаете из значения.

Обещание, по аналогии, является письменной стороной вычисления. Вы создаете обещание, которое является местом, где вы поместите результат вычисления, и с этого обещания вы получите будущее, которое будет использоваться для чтения результата, который был внесен в обещание. Когда вы выполните обещание, либо сбой или успех, вы будете запускать все поведение, которое было привязано к связанному Будущему.

Что касается вашего первого вопроса, как это может быть для обещания p, мы имеем p.future == p. Вы можете представить это как буфер с одним элементом - контейнер, который изначально пуст, и вы можете послесловия сохранить одно значение, которое станет его содержимым навсегда. Теперь, в зависимости от вашей точки зрения, это одновременно и обещание, и будущее. Это обещание для кого-то, кто намеревается записать значение в буфере. Это будущее для тех, кто ждет, чтобы это значение было помещено в буфер.

В частности, для Scala параллельного API, если вы посмотрите на черту Promise в здесь, вы можете увидеть, как методы из объекта-спутника Promise реализовано:

object Promise {

  /** Creates a promise object which can be completed with a value.
   *  
   *  @tparam T       the type of the value in the promise
   *  @return         the newly created `Promise` object
   */
  def apply[T](): Promise[T] = new impl.Promise.DefaultPromise[T]()

  /** Creates an already completed Promise with the specified exception.
   *  
   *  @tparam T       the type of the value in the promise
   *  @return         the newly created `Promise` object
   */
  def failed[T](exception: Throwable): Promise[T] = new impl.Promise.KeptPromise[T](Failure(exception))

  /** Creates an already completed Promise with the specified result.
   *  
   *  @tparam T       the type of the value in the promise
   *  @return         the newly created `Promise` object
   */
  def successful[T](result: T): Promise[T] = new impl.Promise.KeptPromise[T](Success(result))

}

Теперь эту реализацию promises, DefaultPromise и KeptPromise можно найти здесь . Они оба расширяют базовую маленькую черту, которая имеет одно и то же имя, но она находится в другом пакете:

private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with scala.concurrent.Future[T] {
  def future: this.type = this
}

Итак, вы можете видеть, что они означают p.future == p.

DefaultPromise - это буфер, на который я ссылался выше, а KeptPromise - это буфер со значением, введенным из самого его создания.

Что касается вашего примера, будущий блок, который вы там используете, на самом деле создает обещание за кулисами. Давайте рассмотрим определение future в здесь:

def future[T](body: =>T)(implicit execctx: ExecutionContext): Future[T] = Future[T](body)

Следуя цепочке методов, вы попадаете в 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
  }
}

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

LATER EDIT:

Что касается использования в реальном мире: большую часть времени вы не будете иметь дело с promises напрямую. Если вы будете использовать библиотеку, которая выполняет асинхронное вычисление, то вы просто будете работать с фьючерсами, возвращаемыми библиотечными методами. promises в этом случае создаются библиотекой - вы просто работаете с чтением в конце того, что делают эти методы.

Но если вам нужно реализовать собственный асинхронный API, вам придется приступить к работе с ними. Предположим, вам нужно реализовать асинхронный HTTP-клиент поверх, скажем, Netty. Тогда ваш код будет выглядеть примерно так:

    def makeHTTPCall(request: Request): Future[Response] = {
        val p = Promise[Response]
        registerOnCompleteCallback(buffer => {
            val response = makeResponse(buffer)
            p success response
        })
        p.future
    }