Scala by Example: Futures
Managing asynchronous computation with this code example showing Future creation for non-blocking operations, ExecutionContext for thread pool management, callback registration with onComplete, Success and Failure handling, and composing multiple futures using for-comprehensions.
Code
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Success, Failure}
// Define an async task
def fetchData(url: String): Future[String] = Future {
Thread.sleep(500) // Simulate latency
s"Data from $url"
}
val f1 = fetchData("http://example.com")
val f2 = fetchData("http://test.com")
// Callback (onComplete)
f1.onComplete {
case Success(data) => println(s"Got: $data")
case Failure(e) => println(s"Error: $e")
}
// Composing futures (flatMap / for-comprehension)
val combined = for {
r1 <- f1
r2 <- f2
} yield s"$r1 + $r2"
// combined is a Future[String]
combined.foreach(println)Explanation
Future provides a way to reason about performing many operations in parallel in an efficient and non-blocking manner. A Future is a placeholder object for a value that may not yet exist because the computation is still running. The computation is performed asynchronously, typically on a separate thread pool provided by the ExecutionContext. The global execution context uses a fork-join pool sized according to available processors, making it suitable for CPU-bound tasks.
You can attach callbacks like onComplete to handle the result once it becomes available, receiving either Success(value) or Failure(exception). However, the real power of Futures comes from their composability. You can use map, flatMap, filter, and recover to transform and combine Future results without blocking the current thread. These operations return new Futures, allowing you to build complex asynchronous workflows declaratively.
For-comprehensions provide an elegant way to chain multiple asynchronous operations sequentially while maintaining readability. In the example, combined waits for both f1 and f2 to complete, which actually run in parallel since they were started before the for-comprehension, and then yields the combined result. This makes writing complex asynchronous flows straightforward while avoiding callback hell. Futures also support error handling through recover and recoverWith, allowing you to handle failures gracefully and provide fallback values or alternative computations.
Code Breakdown
Future { ... } starts block asynchronously on global execution context.onComplete registers callback handling both success and failure cases.for { r1 <- f1 ... } extracts values once ready, creating new Future.foreach executes side effect when Future completes successfully.
