Custom async iterator in JavaScript
9/3/2021, 7:03:09 AM
You can write an async iterator/generator of your own in JavaScript.
The motivation behind the idea was that
- I wanted to call AWS.Comprehend.batchDetectEntities and pass 309 items of texts in an array.
- When I did the first time, AWS returned "Throttoling Exception". It turned out that the current settings allow only 10 items per second and a "batch" call is considered as N calls (number of calls in a batch call) not a call for the quota.
- So I thought I would split the array of texts into pieces so each piece has 10 items in it, and call the API over and over with 2 seconds interval.
It would be something like this:
async function getPiece(data) {
const piece = data.splice(0, Math.min(10, data.length))
await new Promise((resolve) => setTimeout(resolve, 2000));
return piece
}
const data = [ ... ]
(async () => {
const results = []
const cloned = [...data]
while (cloned.length > 0) {
const piece = await getPiece(cloned)
const result = await AwsApiCall(piece)
results.push(result)
}
console.log(results)
})()
It waits 2 seconds and then pulls out the first 10 items from inputs and calls AWS API with the set of 10 items, process the result and see if there are more inputs.
It works, but I felt itchy because the content of the inputs or
cloned
at a particular moment was ambiguous from the code. The getPiece
function modifies the content of cloned
which is not obvious from the main function, but the main function is relying on the fact that the content would eventually become empty.There could be many ways to write cleaner code than this. For example, the number of times the loop in the main function should iterate can be pre-calculated in the main function, thus it's not necessary for the main function to rely on the
getPiece
function to manage the content of inputs.But I found it easy to use a generator/iterator here. The similar code could be written like this if we use an async generator:
async function* dataSplitter(data) {
const cloned = [...data]
while (cloned.length > 0) {
const piece = cloned.splice(0, Math.min(10, cloned.length))
await new Promise((resolve) => setTimeout(resolve, 2000));
yield piece
}
}
const data = [ ... ]
(async () => {
const results = []
for await (const piece of dataSplitter(data)) {
const result = await AwsApiCall(piece)
results.push(result)
}
console.log(results)
})()
The
dataSplitter
yields 10 items every 2 seconds until there are no more data, and the main function doesn't have to know how it manages the content of the data. All the main function has to do is to call the AWS API for each piece that the generator yields in the for await of
loop which was also new to me.