I'm consuming an API with rate-limit, every time a hit my rate limit it returns header retry-after specifying the amount of seconds to wait for rate limit reset.
I need to:
My solution so far:
async *indicators(items: string[]): AsyncIterableIterator<any[]> {
const res = await Promise.allSettled(items.map((item) => this.makeRequest(item)))
const fulfilledRequests = res.filter((r) => r.status === 'fulfilled') as PromiseFulfilledResult<any>[]
for (const { value } of fulfilledRequests) {
console.log('Yielding')
yield value
console.log('Yielded')
}
const rejectedRequest = res.find((r) => r.status === 'rejected') as any
const failedItems = res.filter((p) => p.status === 'rejected').map(({ reason }: any) => reason.item)
if (failedItems.length === 0 || !failedItems?.reason?.retryAfter)
return Logger.log(`No more items to check`)
setTimeout(this.indicators(failedItems).next.bind(this), rejectedRequest.reason.retryAfter)
}
async makeRequest(item: string): Promise<Indicator[]> {
try {
const { data: { data } } = await firstValueFrom(this.httpService.post('https://api.io', { item }))
return data
} catch (error) {
throw { retryAfter: error.response.headers['retry-after'] * 1000, symbol }
}
}
main() {
for await (const item of this.indicators(['', ''])) {
console.log(item)
}
}
Yielding log is shown and then it stops.I'm using NestJS with Typescript on Node v16.
I'd suggest retry every request independently until succeed or abandond.
The code below is NOT TESTED
async function main() {
const promises = ["", ""].map(item=>makeRequestUntilDone(item));
const results = await Promise.allSettled(promises);
for(const r of results) {
if(r.status === 'fulfilled') {
// process result
console.log(r.value);
} else {
// process error if you sometimes
// throw in `makeRequestUntilDone`
console.log(r.reason);
}
}
}
async function makeRequestUntilDone(item: string) {
while(true){
try {
const { data: { data } } = await firstValueFrom(this.httpService.post('https://api.io', { item }))
return data
} catch (error) {
// `throw error` if you don't wanna retry
// anymore or it is not a retryable error,
// otherwise delay and continue
const retryAfter = error.response.headers['retry-after'] * 1000;
await delay(retryAfter);
}
}
}
function delay(ms: number) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
then you can
I made it working by using a do while loop awaiting for retry after time.
async *listMeetings(meetingIds: string[]): AsyncIterableIterator<MeetingResponse> {
let hasMore = false
do {
const res = await Promise.allSettled(meetingIds.map((id) => this.makeRequest(id)))
const fulfilledRequests = res.filter((r) => r.status === 'fulfilled')
for (const { value } of fulfilledRequests) {
yield value
}
const rejectedRequest = res.find((r) => r.status === 'rejected')
const failedMeetings = res.filter((p) => p.status === 'rejected').map(({ reason }: any) => reason.meetingId)
if (failedMeetings.length === 0 || !rejectedRequest?.reason?.retryAfter) {
hasMore = false
} else {
await new Promise<void>((resolve) => setTimeout(() => resolve(), rejectedRequest.reason.retryAfter))
yield* this.listMeetings(failedMeetings)
}
} while (hasMore)
}
main() {
for await (const meeting of this.client.listMeetings([])) {
console.log(meeting)
}
}