В чём преимущества Promise и как он используется?

Promise в Scala — это механизм, позволяющий вручную управлять завершением асинхронной операции, которая будет представлена соответствующим Future. Если Future — это "обещание получить результат позже", то Promise — это то, что даёт обещание. Он даёт возможность создать Future, который можно завершить извне — с успехом или ошибкой. Это удобно в ситуациях, когда результат операции известен не сразу и не может быть вычислен внутри самого Future.

Создание и основное использование

import scala.concurrent.{Promise, Future}
import scala.concurrent.ExecutionContext.Implicits.global
val promise = Promise\[Int\]()
val future: Future\[Int\] = promise.future
// Где-то позже, в другом потоке:
promise.success(42)
// или promise.failure(new Exception("Ошибка"))

Метод promise.success(value) завершает Future с успехом, а promise.failure(ex) — с ошибкой. После вызова одного из этих методов, Future, связанный с этим Promise, считается завершённым.

Зачем нужен Promise

1. Контроль над завершением Future вручную

Иногда нельзя описать асинхронную логику с помощью обычного Future, особенно если результат зависит от внешнего события (например, ответа из сторонней библиотеки или callback-а).

def asyncWithCallback(): Future\[String\] = {
val promise = Promise\[String\]()
someJavaApi.registerCallback(result => promise.success(result))
promise.future
}

2. Связка с Java API (callback-интерфейсы)

Многие Java-библиотеки используют колбэки вместо Future. В этом случае Promise помогает обернуть старый API в Future-подобную модель.

def wrapCallbackAPI(): Future\[String\] = {
val p = Promise\[String\]()
legacyApi.callAsync(
new Callback {
override def onSuccess(data: String): Unit = p.success(data)
override def onFailure(ex: Throwable): Unit = p.failure(ex)
}
)
p.future
}

3. Оркестрация и координация асинхронных операций

Можно использовать Promise для того, чтобы "удерживать" завершение, пока не выполнится определённое условие.

def waitForSignal(signal: Promise\[Unit\]): Future\[Unit\] = signal.future
val signal = Promise\[Unit\]()
// где-то в другом месте
signal.success(())

4. Повторное использование Future

Так как Promise создаёт Future, которым можно делиться между разными участниками программы, удобно использовать его как "сигнал готовности" между потоками.

Пример: ожидание завершения внешнего события

def simulateExternalEvent(p: Promise\[String\]): Unit = {
Thread.sleep(1000)
p.success("Event completed")
}
val p = Promise\[String\]()
Future {
simulateExternalEvent(p)
}
p.future.map(result => println(s"Result: $result"))

Здесь Future создаётся один раз, а результат устанавливается позже извне.

Методы Promise

  • success(value: T): Unit — завершает Future с результатом.

  • failure(ex: Throwable): Unit — завершает с ошибкой.

  • trySuccess(value: T): Boolean — возвращает true, если Promise ещё не был завершён.

  • tryFailure(ex: Throwable): Boolean — аналогично trySuccess, но для ошибок.

  • future: Future[T] — доступ к связанному Future.

Потокобезопасность

Promise — потокобезопасен: можно вызывать success, failure или try* из разных потоков. Однако только один вызов может успешно завершить Promise. Повторные вызовы будут проигнорированы (если используются try*), либо приведут к исключению (если success или failure).

Пример: несколько потребителей одного результата

val promise = Promise\[Int\]()
val f = promise.future
f.foreach(v => println(s"Consumer 1: $v"))
f.foreach(v => println(s"Consumer 2: $v"))
promise.success(123)
// Оба подписчика получат значение

Ошибки при неправильном использовании

Если вызвать success или failure повторно после завершения, произойдёт исключение:

promise.success(1)
promise.success(2) // java.lang.IllegalStateException: Promise already completed

Чтобы избежать этого — используйте trySuccess или tryFailure.

Объединение с Future

Иногда удобно комбинировать Promise с Future:

val p = Promise\[Int\]()
val computation = Future {
val result = 1 + 1
p.success(result)
}

Однако такой способ менее гибкий, чем просто использовать Future { ... } напрямую. Преимущество Promise — в отсроченном, внешнем завершении.

Когда использовать Promise, а когда нет

Используй Promise, когда:

  • нужно завершить Future извне;

  • асинхронная операция запускается внешней системой через callback;

  • требуется создать "ручной" сигнал завершения.

Не используй Promise, если:

  • результат можно получить сразу или через Future { ... };

  • тебе не нужно вручное управление результатом.