पाठ 16 / 25
Futures and Asynchronous Code
Run asynchronous computations with Future and compose them.
Futures in the standard library
A Future[A] represents a value that will be available later, computed on an ExecutionContext (a thread pool). Future { ... } starts running immediately, which is called eager evaluation. Futures compose with map, flatMap, recover, recoverWith and for comprehensions, so dependent asynchronous steps read sequentially, while Future.sequence and Future.traverse run many in parallel and collect results. Callbacks such as onComplete exist, but composing is cleaner. A subtle point: in a for comprehension, futures are created when their line runs, so to run two independent futures in parallel, create them before the for. Blocking with Await.result is for tests and program edges only; in services, return the Future to the framework. Blocking I/O inside the global execution context should be wrapped in blocking { ... } or run on a dedicated pool. Because Futures are eager and memoised, they are not referentially transparent, which is one reason many teams use effect systems (next topic) instead.
Composing Futures
Independent futures run in parallel and are combined when both finish.
Parallel and sequential Futures
Start independent work first, then combine with for.
import scala.concurrent.{Future, Await}
import scala.concurrent.duration.*
import scala.concurrent.ExecutionContext.Implicits.global
case class Customer(id: String, name: String)
case class Order(id: String, totalPaise: Long)
def fetchCustomer(id: String): Future[Customer] = Future { Thread.sleep(200); Customer(id, "Asha") }
def fetchOrders(id: String): Future[List[Order]] = Future { Thread.sleep(200); List(Order("o1", 120000)) }
def dashboard(id: String): Future[String] =
val customerF = fetchCustomer(id) // both start now, in parallel
val ordersF = fetchOrders(id)
for
customer <- customerF
orders <- ordersF
yield s"${customer.name}: ${orders.size} orders, total ${orders.map(_.totalPaise).sum} paise"
val safe: Future[String] = dashboard("c-1").recover { case e: Exception => s"unavailable: ${e.getMessage}" }
val all: Future[List[Customer]] = Future.traverse(List("c-1", "c-2", "c-3"))(fetchCustomer)
@main def runDashboard(): Unit =
println(Await.result(safe, 2.seconds)) // blocking is acceptable only at the program edge
println(Await.result(all, 2.seconds).map(_.name))Create futures before the for
Writing for a <- fetchA(); b <- fetchB() yield ... runs the calls one after the other, because fetchB is only called after fetchA completes. Assign both futures to vals first to run them in parallel.
त्वरित जाँच: In a for comprehension over Futures, how do you make two independent calls run in parallel?
- Use two separate for loops
- Use Await.result inside the for
- Start both futures (assign them to vals) before the for comprehension
- It always runs in parallel
Answer
Start both futures (assign them to vals) before the for comprehension — Futures start when created, so creating them first lets them run concurrently.