Today is the first time I actually use generators in javascript (typescript) to do something useful, in this case it was iterating over objects reachable through a REST painated api.
Initially the code to query one page of data from the API looked like this
1
2
3
4
5
6
const getProjects = (userId: number, page = 1): Promise<Response> => {
return axios.get(
`https://example.com/api/v1/user/${userId}/projects`,
{ page },
)
}
To explain that code line by line
Promise
of an object with the Response
structure. A function that returns a Promise
is equivalent to an async function
, and both can be await
eduserId
inside the stringthe structure of the response in typescript is like the following
interface Project {
id: number
name: string
}
interface Response {
total_count: number
per_page: number
data: Array<Project>
}
Note that the data of the projects is inside the data
attribute and the total_count and per_page are being returned as data in the response.
Then I created a generator to abstract the pagination out. It iterates page by page on the api and returns (yields) every object inside the array directly. An infamous do {} while ()
loop is used to avoid repeating code, because it needs to be executed at least once.
1
2
3
4
5
6
7
8
9
10
11
async function *getProjectsPaginatedGenerator(userId: number) => {
let page = 0
let res
do {
page += 1
res = await getProjects(userId, page)
for (const project of res.data) {
yield project
}
} while (res.total_count > page * res.per_page)
}
To explain that code line by line
page
variable to zerores
variable in the scope outside the body of the do..while
looppage
to the next one (first time this will be one)Response
object saved in the res
variable. This await
call is blocking. It the promise fails (i.e. network error, or server response status code >= 400), it throws an exception and halts execution.Project
) inside the Response.data
yield
s every object separately. Generator magic happens here.As a bonus, if throttling is needed, a simple solution can be to use a js sleep function like the following one, and await
to it just before ending the do..while
loop.
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))
An example of the generator in use
const userId = 1
for await (const project of getProjectsPaginatedGenerator(userId)) {
console.log(project)
}
The for..of
can iterate throuh a generator items that are being yielded async. The awesomeness here is that the iteration is done directly throuh objects and the pagination is not even visible by the use of the generator.