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))

Leave a Comment

Your email address will not be published. Required fields are marked *