Iterate sequence of future in sequence of futures

Viewed 2012

I am working on a PlayFramework application written in Scala.

Problem is that in a rest controller I need a list of elements (books) and for each element list of its subelements (chapters).

Book repository:

def findAll(): Future[Seq[Book]] 

Chapter repository:

def findByBookId(bookId: UUID): Future[Seq[Chapter]]

I wanted to do something like

val books = bookRepository.findAll
val result = for {
  bookList <- books
  book <- bookList
  chapters <- chapterRepository.findByBookdId(book.id)
} yield (book, chapters)

I want to have a tuple of book and its chapters so I can latter map it to a json. What I am doing wrong, because I get error:

[error]  required: scala.collection.GenTraversableOnce[?]

Or what would be a better approach how to iterate over future of collection and for each element load another future of collection?

3 Answers

See if this gets you close to what you're after.

// found books (future)
val fBooks :Future[Seq[Book]] = bookRepository.findAll()

// chapters in fBooks order (future)
val fChptrs :Future[Seq[Seq[Chapter]]] = fBooks.flatMap {books =>
  Future.sequence(books.map(book =>findByBookId(book.id)))
}

// combine book with its chapters
val result :Future[Seq[(Book, Seq[Chapter])]] = for {
  books <- fBooks
  chptrs <- fChptrs
} yield books zip chptrs

You should use the same type throughout the for-comprehension.

You can't have books:Future[Seq[Books] and bookList: Seq[Book] in same for-comprehension.

Also you can convert Seq[Future[Something]] to Future[Seq[Something]] using Future.sequence

So, something like this should work

val res: Future[(Seq[Book], Seq[Chapter])] =
for {
  bookList <- books
  chapters <- Future.sequence(bookList.map(book => findByBookId(book.id)))
} yield (bookList, chapters.flatten)
Related