2013-11-29 3 views
20

Снова и снова я борюсь, когда функция зависит от некоторых будущих результатов. Это обычно сводится к следующему результату, как Future [Seq [Future [MyObject]]]Избавьтесь от Scala Будущее гнездования

Чтобы избавиться от этого, теперь я использую 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)) 
     } 
    }) 
} 

Этот код работает, но для меня это выглядит довольно Hacky. Два вызова карты - еще одна ошибка. Я потратил часы, пытаясь понять, как сохранить это полностью асинхронным и вернуть простой будущий Seq. Как это можно решить, используя лучшие практики Play2?

Редактировать Чтобы сделать UseCase более ясным:

У меня есть объект А из MongoDB (reactivemongo) и хотите добавить информацию, поступающую от другого вызова MongoDB getShortInfo. Это классический случай «получить пользователя для этого сообщения», который будет разрешен с присоединением к РСУБД. getShortInfo, естественно, создавал бы Будущее из-за вызова на БД. Чтобы уменьшить вложенность в пределах findAll Я использовал Await(). Это хорошая идея?

findAll вызывается из асинхронного игрового действия, преобразованного в 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

К сожалению , Я не понимаю, на что вопрос. Если вы просто хотите фильтровать или обрабатывать значения завернутого Seq в будущем, то ваше решение кажется прекрасным. Нет ничего плохого в том, что у вас есть два вызова карты; что является нормальным и необходимым, поскольку первая карта находится над Будущим, а вторая - над Seq. Я не вижу необходимости в Await here; вы говорите, что это уже происходит внутри UserDao.getShortInfo? –

+1

Итак, я думаю, что ваш вопрос: если вы не использовали Await внутри UserDao.getShortInfo, тогда он бы создал Future [JsObject]. и выход этого FindAll был бы Future [Seq [Future [Idea]]], который не то, что вы хотите. Тогда возникает вопрос, чего вы хотите. Вы уверены, что вам понадобится Future [Seq [Idea]], который может разрешить только один раз, когда все элементы были обработаны? Возможно, вы предпочтете Seq [Future [Idea]], поскольку элементы, как представляется, обрабатываются независимо в любом случае? –

+0

Тем временем я узнал, что всегда можно избавиться от всех операций блокировки. При использовании некоторых методов внутри этих ответов и сопоставления операций над операциями, которые возвращают будущее, вам больше не понадобится использовать ожидание. – DanielKhan

ответ

45

Две удобные функции на объекте сопутствующего будущего, который вы должны знать, могут помочь здесь, первая, и проще обернуть вокруг вас - Future.sequence. Он занимает некоторое будущее и возвращает будущее последовательности. Если заканчивается Future[Seq[Future[MyObject]]], позвоните нам result. то вы можете изменить это к Future[Future[Seq[MyObject]]] с result.map(Future.sequence(_))

Тогда, чтобы свернуть Future[Future[X]] для любого X, вы можете запустить «result.flatMap (идентичность)», на самом деле, вы можете сделать это для любого M[M[X]] создать M[X] как длинный как M имеет flatMap.

Еще одна полезная функция здесь - Future.traverse. Это в основном результат принятия Seq[A], сопоставление его с Seq[Future[B]], а затем запуск Future.последовательность, чтобы получить Future[Seq[B]] Таким образом, в вашем примере, вы бы:

ideas.map{ Future.traverse(_){ idea => 
    /*something that returns a Future[JsObject]*/ 
} }.flatMap(identity) 

Однако, много раз, когда вы работаете flatMap (удостоверение), вы могли бы токарных карту в flatMap, и это тот случай, здесь:

ideas.flatMap{ Future.traverse(_) { idea => 
    /*something that returns a Future[JsOjbect]*/ 
} } 
4

Akka documentation имеет хороший обзор о том, как иметь дело с композициями фьючерсов. В общем, описывает четыре метода в scala.concurrent.Future, которые могут быть использованы для уменьшения состава фьючерсов в единое будущем Например:

  • Future.sequence
  • Future.traverse
  • Future.fold
  • Future.reduce
Смежные вопросы