有比自定义迭代器更好的处理分页的解决方案吗?

Any better solution than custom Iterators for handling pagination?

我正在使用 API 对其结果进行分页,即 API returns 包含布尔标志“isLastPage”的响应,指示是否还有任何记录, 我的意图是在调用数据库时一次“产生”一条记录。

我是 Scala 的新手,希望能够创建类似于 Python 生成器的东西,其中每次调用都会产生一个项目并且上下文持续存在,但是我找不到这样的解决方案在 Scala 中(无论如何都不是一个很好的)所以我通过扩展 Iterator 解决了这个问题, 我对这个解决方案不是很满意,因为它依赖于可变性。

这是个好方法吗?我很想获得一些意见和更好的选择

这是我的代码模拟,仅供说明: 我在这里将分页数据表示为 Page 和具有 accessData 函数的数据库客户端库,入口点是 iterateItems:

case class Page(items: List[Int], nextPage: Option[Int], isLastPage: Boolean = false)

def accessData(nextPage: Option[Int]): Option[Page] = {
  val data = List(Page(List(1, 2, 3), Some(1)),
    Page(List(4, 5, 6), Some(2)),
    Page(List(7, 8, 9), Some(3)),
    Page(List(10, 11, 12), None, isLastPage = true))
    val item = Option(nextPage.fold(data(0))(idx => data(idx)))
    println(f"accessing data $item")
    Thread.sleep(50)
    item

}

class PageIterator extends Iterator[Page] {
  var hasNextPage = true
  var nextPage: Option[Int] = None
  override def hasNext = hasNextPage
  override def next(): Page = {
    val page = accessData(nextPage).get
    nextPage = page.nextPage
    hasNextPage = !page.isLastPage
    page
  }
}

def iterateItems = (new PageIterator).flatMap(_.items)


iterateItems.foreach(item => println("now working on " + item))

谢谢!

就像我常说的,Scaladoc 是你的朋友;您可以在 Iterator

上使用 unfold 方法
def accessData(nextPage: Int = 0): Page = ???

Iterator.unfold((0, true)) {
  case (idx, true) =>
    val page = accessData(idx)
    Some(((idx + 1), !page.isLastPage) -> page)

  case (_, false) =>
    None
}.flatMap(_.items)

PS:由于您可能正在进行异步调用,因此您会想在那里混合使用 Future 之类的东西,然后混合使用 FuturesIterator不简单,容易出错
我的建议是使用 fs2 Stream cats-effect IO 而不是 Iterator & Future,解决方案类似,但使用 unfoldChunkEval 代替。

其他选择可能是 Akka Streams & ZIO.