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.