Scala

Chaining futures in scala

Suppose I want to make coffee. This involves 4 steps:

  • 1a. grind coffee beans
  • 1b. heat water
  • 2. combine
  • 3. filter

All these steps take time, so they return a Future. This is our domain:
 
 

import scala.concurrent.{Await, Future}
import scala.concurrent.ExecutionContext.Implicits.global //We need an executionContext to run futures
import scala.concurrent.duration._ //This provides the "1 second" syntax

class CoffeeBeans()
class GroundCoffee()
class ColdWater()
class WarmWater()
class UnfilteredCoffee()
class FilteredCoffee()

//we start out with beans and cold water
val beans = new CoffeeBeans()
val water = new ColdWater()

def grindBeans(beans: CoffeeBeans) = Future { new GroundCoffee() }
def heatWater(water: ColdWater) = Future { new WarmWater() }
def combine(groundCoffee: GroundCoffee, water: WarmWater) = Future { new UnfilteredCoffee() }
def filter(unfilteredCoffee: UnfilteredCoffee) = Future { new FilteredCoffee() }

One way we could chain the futures together is with onSuccess:

val fGrind: Future[GroundCoffee] = grindBeans(beans)
val fHeat: Future[WarmWater] = heatWater(water)
val fStep1: Future[(GroundCoffee, WarmWater)] = fGrind.zip(fHeat)

fStep1.onSuccess { case (groundCoffee, warmWater) =>
  val fCombine: Future[UnfilteredCoffee] = combine(groundCoffee, warmWater)
  fCombine.onSuccess { case unfilteredCoffee =>
    val fFilter: Future[FilteredCoffee] = filter(unfilteredCoffee)
    fFilter.onSuccess { case successCoffee =>
      println(s"$successCoffee is ready!")
    }
  }
}
Thread.sleep(1000)//wait for the coffee to be ready

Using flatmap we can simplify this to:

val fGrind: Future[GroundCoffee] = grindBeans(beans)
val fHeat: Future[WarmWater] = heatWater(water)
val fStep1: Future[(GroundCoffee, WarmWater)] = fGrind.zip(fHeat)

val fCombine: Future[UnfilteredCoffee] = fStep1.flatMap {
  case (groundCoffee, warmWater) => combine(groundCoffee, warmWater)
}
val fFilter: Future[FilteredCoffee] = fCombine.flatMap {
  case unfilteredCoffee => filter(unfilteredCoffee)
}
val flatmapCoffee: FilteredCoffee = Await.result(fFilter, 1 second)

Notice that we have the future of our filtered coffee at the highest level. This is nice! We can return it, or call Await.Result on it to wait for our coffee to be ready.

Since a for comprehension is a shorter way of writing flatMaps, we can simplify it to this:

val fFor = for {
  groundCoffee <- grindBeans(beans)
  warmWater <- heatWater(water)
  unfilteredCoffee <- combine(groundCoffee, warmWater)
} yield filter(unfilteredCoffee)
val forCoffee: FilteredCoffee = Await.result(fFilter, 1 second)

cheers!

Reference: Chaining futures in scala from our JCG partner Tammo Sminia at the JDriven blog.

Arthur Arts

Arthur Arts is an experienced Enterprise Java software engineer and hands-on Agile Coach and Scrum Master. He has a strong focus on agile engineering principles and practices and loves to share his knowledge and learn from others. He has his own company Coded Value, is a passionate photographer and writes for agilearts.nl.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button