我正在使用返回分页资源的服务。它公开了一个单一的调用,它由以下接口定义:
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}}。
你能指导我如何实现这个目标吗?
答案 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)
这更清洁,完全无阻塞。但如果这是合适的话,则由您的用例决定。