使用一个分页的Iterable [T],好像它是一个连续的Iterable [T]

时间:2017-08-29 12:05:43

标签: scala scala-collections

我正在使用返回分页资源的服务。它公开了一个单一的调用,它由以下接口定义:

trait Service {
    getPage(pageSize: Int, pageCursor: String): AsyncPage[Resource]
}

getPage函数返回一个AsyncPage[T]对象,其实现如下:

/**
 * A page of contents that are retrieved asynchronously from their origin
 *
 * @param content The resource object
 * @param nextPageCursor The token representing the next page, or empty if no more pages to consume
 * @tparam T The type of resource withing the page
 */
case class AsyncPage[T](
  val content: Future[Iterable[T]],
  val nextPageCursor : Future[String]
) { }

从服务使用的任何存储系统异步检索页面内容。

由于我的申请需要,我并不关心网页。我想编写允许我使用服务资源的代码,就像它是一个Iterable[T]一样。

但是,我想保持服务的懒惰。我不想要求超过必要的页面。这意味着我不想要求下一页,直到我没有消耗前一页的所有元素。

每当我消耗了一页的整个Iterable[T]时,我希望代码使用getPage(...)函数请求以下页面,并提供最后一页的pageCursor参数{ {1}}。

你能指导我如何实现这个目标吗?

2 个答案:

答案 0 :(得分:1)

好吧,如果你不介意阻止,你可以这样做:

 class FutureIter[+P](fu: => Future[Iterator[P]]) extends AbstractIterator[P] {
   lazy val iter = Await.result(fu)
   def hasNext = iter.hasNext
   def next = iter.next 
 }

  def fold[T](fs: Stream[Future[Iterator[T]]]): Iterator[T]= fs match {
    case hd #:: tail => new FutureIter(hd) ++ fold(tail)
    case _ => Iterator.empty
  }

  val pages = Stream
    .iterate(getPage(size, "")) { getPage(size, _.nextPageCursor) }
    .map(_.contents.map(_.iterator))

  val result: Iterator[T] = fold(pages)

这将在第一页之前阻止,并在每个后续页面的末尾加载以加载下一批。我认为没有阻止的方法可以做到这一点,因为在未来满意之前你无法分辨页面的结束位置。

另外,请注意,此代码生成的迭代器是无限的,因为您没有提及何时停止查找更多页面的任何条件。您可以将.takeWhile一些pages号召唤到Stream进行更正。

您可能还希望将Iterator替换为Stream,以便您完成的页面立即被丢弃,而不是被缓存。我刚刚使用fold,因为这会使if(it.hasNext) ...更好一些(你无法在迭代器上匹配,而是必须使用丑陋的fold)。

BTW,++看起来像是递归的,但它实际上是不是fold(tail)是懒惰的,所以fold块才会被执行,直到在外部az image create离开堆栈之后,你已经一直在左侧进行迭代。

答案 1 :(得分:0)

由于您提到了akka,您可以创建一个Source[T],它可以作为异步Iterable[T]进行排序:

Source.unfoldAsync[String, T](startPageCursor) { cursor =>
  val page = getPage(pageSize, cursor)
  for {
    nextCursor <- page.nextPageCursor
    it <- page.content
  } yield Some((nextCursor, it))
}.mapConcat(identity)

这更清洁,完全无阻塞。但如果这是合适的话,则由您的用例决定。