有比自定义迭代器更好的处理分页的解决方案吗?
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))
- 请注意我必须如何在响应中依赖 isLastPage 来了解是否需要对 API.
进行另一个调用
- 我在这里将 nextPage 表示为 int,但实际上它没有任何我可以依赖的连续质量
谢谢!
就像我常说的,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
之类的东西,然后混合使用 Futures
和 Iterator
不简单,容易出错
我的建议是使用 fs2 Stream
和 cats-effect IO
而不是 Iterator
& Future
,解决方案类似,但使用 unfoldChunkEval
代替。
其他选择可能是 Akka Streams & ZIO.
我正在使用 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))
- 请注意我必须如何在响应中依赖 isLastPage 来了解是否需要对 API. 进行另一个调用
- 我在这里将 nextPage 表示为 int,但实际上它没有任何我可以依赖的连续质量
谢谢!
就像我常说的,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
之类的东西,然后混合使用 Futures
和 Iterator
不简单,容易出错
我的建议是使用 fs2 Stream
和 cats-effect IO
而不是 Iterator
& Future
,解决方案类似,但使用 unfoldChunkEval
代替。
其他选择可能是 Akka Streams & ZIO.