Избавиться от Scala Будущее гнездование
Снова и снова я борюсь, когда функция опирается на некоторые будущие результаты.
Это обычно сводится к результату, например Future [Seq [Future [MyObject]]]
Чтобы избавиться от этого, я теперь использую Await внутри вспомогательной функции, чтобы вывести объект, не являющийся будущим, и уменьшить вложенность.
Похоже на это
def findAll(page: Int, perPage: Int): Future[Seq[Idea]] = {
val ideas: Future[Seq[Idea]] = collection.find(Json.obj())
// [...]
ideas.map(_.map { // UGLY?
idea => {
// THIS RETURNED A Future[JsObject] before
val shortInfo: JsObject = UserDao.getShortInfo(idea.user_id)
idea.copy(user_data = Some(shortInfo))
}
})
}
Этот код работает, но для меня он выглядит довольно взломанным. Два вызова карты - еще одна ошибка.
Я потратил часы, пытаясь понять, как сохранить это полностью асинхронным и вернуть простой будущий Seq. Как это можно решить, используя лучшие методы Play2?
Edit
Чтобы сделать usecase более понятным:
У меня есть объект A от mongodb (reactivemongo) и хочу добавить информацию, поступающую с другого вызова в mongodb getShortInfo
. Это классический случай "получить пользователя для этого сообщения", который будет разрешен с присоединением к РСУБД.
getShortInfo
, естественно, создаст будущее из-за вызова в db.
Чтобы уменьшить вложенность в findAll
, я использовал Await(). Это хорошая идея?
findAll
вызывается из асинхронного действия Play, преобразованного в Json и отправленного по проводу.
def getIdeas(page: Int, perPage: Int) = Action.async {
for {
count <- IdeaDao.count
ideas <- IdeaDao.findAll(page, perPage)
} yield {
Ok(Json.toJson(ideas))
}
}
Поэтому я думаю, что возвращение Seq[Future[X]]
из findAll не принесет лучшую производительность, так как я должен ждать результата в любом случае. Правильно ли это?
Краткая справка:
Возьмите вызов Future, возвращающий последовательность, используйте каждый элемент результата для создания другого вызова Future, возвращайте результат к асинхронному действию таким образом, чтобы не возникало никаких ситуаций блокировки.
Ответы
Ответ 1
Две удобные функции на сопутствующем объекте Future, который вы должны знать, могут помочь здесь, первая, и проще обернуть голову вокруг. Future.sequence
. Он занимает некоторое будущее и возвращает будущее последовательности. Если заканчивается с помощью Future[Seq[Future[MyObject]]]
, вызывается, что result
. то вы можете изменить это на Future[Future[Seq[MyObject]]]
с помощью result.map(Future.sequence(_))
Затем, чтобы свернуть a Future[Future[X]]
для любого X, вы можете запустить "result.flatMap(identity)", на самом деле вы можете сделать это для любого M[M[X]]
для создания M[X]
, пока M
имеет flatMap
.
Еще одна полезная функция здесь - Future.traverse
. Это в основном результат принятия Seq[A]
, сопоставление его с Seq[Future[B]]
, а затем запуск Future.sequence для получения Future[Seq[B]]
So в вашем примере:
ideas.map{ Future.traverse(_){ idea =>
/*something that returns a Future[JsObject]*/
} }.flatMap(identity)
Однако, во многих случаях, когда вы выполняете flatMap (identity), вы можете превратить карту в flatMap, и это имеет место здесь:
ideas.flatMap{ Future.traverse(_) { idea =>
/*something that returns a Future[JsOjbect]*/
} }
Ответ 2
документация Akka содержит хороший обзор того, как бороться с композициями фьючерсов. В общем, в нем описываются четыре метода в scala.concurrent.Future, которые могут быть использованы для уменьшения состава фьючерсов в одном экземпляре Future:
-
Future.sequence
-
Future.traverse
-
Future.fold
-
Future.reduce