scalaintermediate

Future Async Patterns

Work with Scala Futures: composition, error handling, timeout, retry, and parallel execution.

scala
import scala.concurrent.*
import scala.concurrent.duration.*
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Success, Failure, Try}

// Simulated async operations
def fetchUser(id: Int): Future[String] = Future {
  Thread.sleep(100)
  if id > 0 then s"User-$id"
  else throw RuntimeException(s"Invalid ID: $id")
}

def fetchProfile(name: String): Future[Map[String, String]] = Future {
  Thread.sleep(50)
  Map("name" -> name, "level" -> "pro")
}

def fetchOrders(userId: String): Future[List[String]] = Future {
  Thread.sleep(75)
  List(s"$userId-order1", s"$userId-order2")
}

@main def run(): Unit =
  // Sequential composition
  val sequential = for
    user    <- fetchUser(1)
    profile <- fetchProfile(user)
    orders  <- fetchOrders(user)
  yield (user, profile, orders)

  val result = Await.result(sequential, 5.seconds)
  println(s"Sequential: $result")

  // Parallel execution
  val f1 = fetchUser(1)
  val f2 = fetchUser(2)
  val f3 = fetchUser(3)
  val parallel = for
    u1 <- f1
    u2 <- f2
    u3 <- f3
  yield List(u1, u2, u3)
  println(s"Parallel: ${Await.result(parallel, 5.seconds)}")

  // Future.sequence: List[Future[A]] => Future[List[A]]
  val futures = (1 to 5).map(fetchUser).toList
  val allUsers = Future.sequence(futures)
  println(s"All: ${Await.result(allUsers, 5.seconds)}")

  // Future.traverse: like sequence + map
  val traversed = Future.traverse(List(1, 2, 3))(fetchUser)
  println(s"Traversed: ${Await.result(traversed, 5.seconds)}")

  // Error handling
  val failing = fetchUser(-1)
  val recovered = failing.recover {
    case e: RuntimeException => s"Fallback: ${e.getMessage}"
  }
  println(s"Recovered: ${Await.result(recovered, 5.seconds)}")

  // recoverWith (returns Future)
  val retried = fetchUser(-1).recoverWith {
    case _ => fetchUser(1)  // retry with valid ID
  }
  println(s"Retried: ${Await.result(retried, 5.seconds)}")

  // Transform
  val transformed = fetchUser(1).transform {
    case Success(v) => Success(s"Got: $v")
    case Failure(e) => Success(s"Error: ${e.getMessage}")
  }
  println(s"Transformed: ${Await.result(transformed, 5.seconds)}")

  // Fallback chain
  val fallback = fetchUser(-1)
    .fallbackTo(fetchUser(-2))
    .fallbackTo(fetchUser(1))
  println(s"Fallback: ${Await.result(fallback, 5.seconds)}")

  // firstCompletedOf
  val race = Future.firstCompletedOf(Seq(
    Future { Thread.sleep(200); "slow" },
    Future { Thread.sleep(50); "fast" },
    Future { Thread.sleep(100); "medium" }
  ))
  println(s"Winner: ${Await.result(race, 5.seconds)}")

  // Callback
  fetchUser(1).onComplete {
    case Success(v) => println(s"Callback: $v")
    case Failure(e) => println(s"Callback error: $e")
  }
  Thread.sleep(200)  // wait for callback

  // Zip futures
  val zipped = fetchUser(1).zip(fetchProfile("Alice"))
  println(s"Zipped: ${Await.result(zipped, 5.seconds)}")

  // Filter
  val filtered = fetchUser(1).filter(_.contains("User"))
  println(s"Filtered: ${Await.result(filtered, 5.seconds)}")

Use Cases

  • Asynchronous API calls
  • Parallel execution patterns
  • Error recovery and fallback chains

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.