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 awaiteduserId 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.datayields 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.