Scala Language
фьючерсы
Поиск…
Создание будущего
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object FutureDivider {
def divide(a: Int, b: Int): Future[Int] = Future {
// Note that this is integer division.
a / b
}
}
Весьма просто, метод divide
создает Будущее, которое будет разрешено с частным a
над b
.
Потребление успешного будущего
Самый простой способ использовать успешное будущее - или, скорее, получить ценность в будущем - это использовать метод map
. Предположим , что некоторый код вызывает divide
метод FutureDivider
объекта из «Создание будущего». Например , Что бы код должен выглядеть , чтобы получить частное a
течение b
?
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(quotient)
}
}
}
Потребление неудачного будущего
Иногда вычисление в Будущем может создать исключение, которое приведет к сбою Будущего. В примере «Создание будущего», если код вызова передал 55
и 0
методу divide
? Разумеется, это приведет к ArithmeticException
после попытки разделить на ноль. Как это будет обрабатываться во потребляющем коде? На самом деле существует несколько способов устранения сбоев.
Обработать исключение с помощью recover
и сопоставления шаблонов.
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient recover {
case ex: ArithmeticException => println(s"It failed with: ${ex.getMessage}")
}
}
}
Обрабатывать исключение с failed
проекцией, где исключение становится значением будущего:
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
// Note the use of the dot operator to get the failed projection and map it.
eventualQuotient.failed.map {
ex => println(s"It failed with: ${ex.getMessage}")
}
}
}
Объединение будущего
В предыдущих примерах были продемонстрированы индивидуальные особенности будущего, обработка успешных и неудачных случаев. Обычно, однако, обе функции обрабатываются гораздо более кратко. Вот пример, написанный более аккуратным и реалистичным образом:
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(s"Quotient: $quotient")
} recover {
case ex: ArithmeticException => println(s"It failed with: ${ex.getMessage}")
}
}
}
Секвенирование и перемещение фьючерсов
В некоторых случаях необходимо вычислить переменную величину значений для отдельных фьючерсов. Предположим, что у вас есть List[Future[Int]]
, но вместо этого нужно обработать List[Int]
. Затем возникает вопрос, как превратить этот экземпляр List[Future[Int]]
в Future[List[Int]]
. Для этого существует метод sequence
на объекте компаньона Future
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.sequence(listOfFuture)
В общем случае sequence
является широко известным оператором в мире функционального программирования , который преобразует F[G[T]]
в G[F[T]]
с ограничениями на F
и G
.
Существует альтернативный оператор, называемый traverse
, который работает аналогично, но принимает функцию в качестве дополнительного аргумента. С помощью функции тождества x => x
в качестве параметра она ведет себя как оператор sequence
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.traverse(listOfFuture)(x => x)
Однако дополнительный аргумент позволяет изменять каждый будущий экземпляр внутри данного listOfFuture
. Кроме того, первый аргумент не должен быть списком Future
. Поэтому можно преобразовать пример следующим образом:
def futureOfList: Future[List[Int]] = Future.traverse(List(1,2,3))(Future(_))
В этом случае List(1,2,3)
непосредственно передается в качестве первого аргумента, а функция тождества x => x
заменяется функцией Future(_)
чтобы аналогично обернуть каждое значение Int
в Future
. Преимущество этого заключается в том, что промежуточный List[Future[Int]]
может быть опущен для повышения производительности.
Объединить несколько фьючерсов - для понимания
Понимание - это компактный способ запуска блока кода, который зависит от успешного результата нескольких фьючерсов.
С f1, f2, f3
три Future[String]
, которые будут содержать строки one, two, three
соответственно,
val fCombined =
for {
s1 <- f1
s2 <- f2
s3 <- f3
} yield (s"$s1 - $s2 - $s3")
fCombined
будет Future[String]
содержащий строку one - two - three
только все фьючерсы будут успешно завершены.
Обратите внимание, что здесь подразумевается неявный ExectionContext.
Кроме того, имейте в виду, что для понимания это просто синтаксический сахар для метода flatMap, поэтому построение объектов Future внутри тела исключило бы одновременное выполнение кодовых блоков, заключенных в фьючерсы, и привести к последовательному коду. Вы видите это на примере:
val result1 = for {
first <- Future {
Thread.sleep(2000)
System.currentTimeMillis()
}
second <- Future {
Thread.sleep(1000)
System.currentTimeMillis()
}
} yield first - second
val fut1 = Future {
Thread.sleep(2000)
System.currentTimeMillis()
}
val fut2 = Future {
Thread.sleep(1000)
System.currentTimeMillis()
}
val result2 = for {
first <- fut1
second <- fut2
} yield first - second
Значение, заключенное в result1
объекта result1
, всегда будет отрицательным, а result2
- положительным.
Более подробную информацию о понимании и yield
в целом см. По адресу http://docs.scala-lang.org/tutorials/FAQ/yield.html.