В чём преимущества 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 { ... };
-
тебе не нужно вручное управление результатом.