In my example above, I chose to return {page, continuation}
or nil
here, as they fit nicely into the Stream
APIs. This allows you to build higher-level APIs, and hide the continuation
as an accumulator in Stream.resource/3
, letting you do things like:
defmodule Pagination.Stream do
@spec pages(
:ets.table(),
:ets.match_spec(),
non_neg_integer()
) :: Enumerable.t()
@doc """
Produces a lazy `Stream` of (at most) `page_size`-length lists of objects
returned by a `match_spec` applied to a `table`.
"""
def pages(table, match_spec, per_page) do
not_started = make_ref()
Stream.unfold(
not_started,
fn
^not_started -> Pagination.start_pagination(table, match_spec, per_page)
continuation -> Pagination.next_page(continuation)
end
)
end
@spec objects(
:ets.table(),
:ets.match_spec(),
non_neg_integer()
) :: Enumerable.t()
@doc """
Produces a lazy `Stream` of objects returned by a `match_spec` applied to a `table`,
loaded into memory in `page_size` chunks.
"""
def objects(table, match_spec, per_page) do
pages(table, match_spec, per_page) |> Stream.flat_map(&Function.identity/1)
end
end
This lets you do things like
Pagination.Stream.pages(table, match_spec, page_size) |> Enum.take(2)
to get the first two pages, or
Pagination.Stream.objects(table, match_spec, page_size) |> Enum.take(100)
to get the first 100 objects, regardless of page_size
, but using the pagination mechanism under the hood to limit the number of objects loaded out of :ets
at any point in time.