Scala Promises
A Promise is a writable container that you fulfill exactly once with either a success value or a failure. When you complete a Promise, any Future linked to it receives the result. If a Future represents a computation already running in the background, a Promise represents a computation whose result you will supply manually — from another thread, after a callback, or based on some external condition.
Promise vs Future
Future Promise
──────────────────────────────── ────────────────────────────────
Read-only: you wait for result Write-once: you provide the result
Created by Future { ... } Created by Promise[A]()
Computation runs automatically You decide when/what to complete
Promise[A]
│
├── .future → returns a Future[A] (the read side)
│
├── .success(v) → completes with Success(v)
├── .failure(ex) → completes with Failure(ex)
└── .complete(t) → completes with a Try[A]
Creating and Completing a Promise
import scala.concurrent.{Promise, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Success, Failure}
val promise = Promise[String]()
val future: Future[String] = promise.future
// Attach a callback to the future
future.onComplete {
case Success(value) => println(s"Got: $value")
case Failure(ex) => println(s"Failed: ${ex.getMessage}")
}
// Complete the promise from anywhere
promise.success("Hello from Promise!")
Thread.sleep(100)
// Got: Hello from Promise!
Promise Completed with Failure
import scala.concurrent.{Promise, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Success, Failure}
val promise = Promise[Int]()
val future = promise.future
future.onComplete {
case Success(n) => println(s"Result: $n")
case Failure(e) => println(s"Error: ${e.getMessage}")
}
promise.failure(new RuntimeException("Something went wrong"))
Thread.sleep(100)
// Error: Something went wrong
Completing Promises Conditionally
import scala.concurrent.{Promise, Future}
import scala.concurrent.ExecutionContext.Implicits.global
def fetchData(id: Int): Future[String] =
val promise = Promise[String]()
Future {
Thread.sleep(300) // simulate async work
if id > 0 then
promise.success(s"Data for ID $id")
else
promise.failure(new IllegalArgumentException(s"Invalid ID: $id"))
}
promise.future
fetchData(5).foreach(println) // Data for ID 5
fetchData(-1).failed.foreach(e => println(s"Failed: ${e.getMessage}"))
Thread.sleep(500)
trySuccess and tryFailure — Safe Completion
A Promise can only be completed once. Calling success or failure a second time throws an exception. Use trySuccess and tryFailure when you are not sure if the Promise was already completed:
val promise = Promise[Int]()
promise.trySuccess(42) // true — first completion succeeds
promise.trySuccess(99) // false — already completed, ignored safely
promise.tryFailure(new RuntimeException("too late")) // false — ignored
promise.future.foreach(println) // 42
Thread.sleep(100)
Race Between Two Operations
import scala.concurrent.{Promise, Future}
import scala.concurrent.ExecutionContext.Implicits.global
def firstToFinish[A](f1: Future[A], f2: Future[A]): Future[A] =
val promise = Promise[A]()
f1.onComplete(result => promise.tryComplete(result))
f2.onComplete(result => promise.tryComplete(result))
promise.future
val slowFuture = Future { Thread.sleep(500); "slow result" }
val fastFuture = Future { Thread.sleep(100); "fast result" }
val winner = firstToFinish(slowFuture, fastFuture)
winner.foreach(v => println(s"Winner: $v"))
Thread.sleep(600)
// Winner: fast result
slowFuture (500ms) ──────────────────────► completes (too late)
fastFuture (100ms) ──────► completes ──► Promise.tryComplete ──► Future("fast result")
(first one wins)
Bridging Callback-based APIs
Many Java and JavaScript-style APIs use callbacks. Promises bridge callbacks to the Future world:
import scala.concurrent.{Promise, Future}
import scala.concurrent.ExecutionContext.Implicits.global
// Simulated callback-based API
def legacyFetch(id: Int, onSuccess: String => Unit, onError: Throwable => Unit): Unit =
new Thread(() => {
Thread.sleep(200)
if id > 0 then onSuccess(s"Record_$id")
else onError(new IllegalArgumentException("Bad ID"))
}).start()
// Wrap it in a Promise → Future
def modernFetch(id: Int): Future[String] =
val promise = Promise[String]()
legacyFetch(
id,
result => promise.success(result),
error => promise.failure(error)
)
promise.future
modernFetch(42).onComplete {
case scala.util.Success(r) => println(s"Got: $r")
case scala.util.Failure(e) => println(s"Error: ${e.getMessage}")
}
Thread.sleep(300)
// Got: Record_42
Promise Summary
Action Method
──────────────────────── ───────────────────────────────────
Create a promise val p = Promise[A]()
Get its future p.future
Complete with success p.success(value)
Complete with failure p.failure(exception)
Safe complete (no throw) p.trySuccess(value)
Complete with Try p.complete(Success(v) or Failure(e))
