Статьи

Цепочка фьючерсов в скале

Предположим, я хочу сделать кофе. Это включает 4 шага:

  • 1a. молотые кофейные зерна
  • 1б. нагреть воду
  • 2. объединить
  • 3. фильтр

Все эти шаги требуют времени, поэтому они возвращают будущее. Это наш домен:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
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() }

Один из способов связать фьючерсы — это onSuccess:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
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

Используя flatmap, мы можем упростить это до:

01
02
03
04
05
06
07
08
09
10
11
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)

Обратите внимание, что у нас есть будущее нашего фильтрованного кофе на самом высоком уровне. Это мило! Мы можем вернуть его или позвонить в службу Await. Получите его, чтобы дождаться готовности нашего кофе.

Так как для понимания это более короткий способ написания flatMaps, мы можем упростить его до этого:

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

ура!

Ссылка: Цепочка фьючерсов на scala от нашего партнера JCG Таммо Сминии в блоге JDriven .