How to do retry on status 200 using axios-retry - javascript

I am using axios-retry
I am trying that it will do retry on response condition and not on response status.
My status is 200 I think because of that it not going to retry,
My code is
raxConfig: {
retry: retry,
retryDelay: retryDelay,
httpMethodsToRetry: ["GET"],
statusCodesToRetry: [
[200, 300]
],
shouldRetry: (err) => {
const cfg = rax.getConfig(err);
if (cfg.data.isFinal == true) {
return false
} else {
return true;
}
}

static async event( retry = 5, retryDelay = 10000) {
//default retryDelay 10 sec
const restURL ='www.sdfdsfds.sdfds'
for (let i = 0; i < retry; i++) {
const response = await this.rest(restURL);
if (
response.status == 200
) {
return response.data;
}
await this.delay(retryDelay);
}
return Promise.reject(new Error(400));
}
static delay(milisec = 10000) {
return new Promise(resolve => {
setTimeout(() => {
resolve("resolved");
}, milisec);
});
}

I tried to throw a 500 with an axios interceptor and then I configured an axios-retry... but that turned ugly real quick...
I ended up doing this
if (retry) {
const MAX = 5;
let tried = 0;
do {
response = await axios.get(url, { ...options });
console.warn('try# ', tried++, response.data);
await delay(1000);
} while (response.data.ok !== 1 && tried < MAX);
} else {
response = await axios.get(url, { ...options });
}

Related

Exponential Backoff not returning data in promise

I am not sure if I am approaching this the correct way. I have tried a few different versions of the implementation but am putting the one here that works when the backoff path is NOT used.
So, I have an index.js that is just:
import { Lizard } from './lizard.js';
const lizard = new Lizard();
const global_data = await lizard.global();
console.log(global_data);
In my lizard.js I have a class with functions but for the sake of saving space and noise I will only place the ones that matter here:
export class Lizard {
global() {
const path = '/global';
return this._request(path);
};
_buildRequestOptions(path, params) {
if (isObject(params)) params = querystring.stringify(params);
else params = undefined;
if (params == undefined) path = `/api/v${API_VERSION}${path}`;
else path = `/api/v${API_VERSION}${path}?${params}`;
// Return options
return {
path,
method: 'GET',
host: HOST,
port: 443,
timeout: Lizard.TIMEOUT,
};
};
async _request(path, params) {
const options = this._buildRequestOptions(path, params);
const maxRetries = 10;
function waitFor(milliseconds) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
async function request(options, retries) {
if (retries > 0) {
const timeToWait = 15000 * retries;
console.log(`waiting for ${timeToWait}ms...`);
await waitFor(timeToWait);
}
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let body = [];
res.on('data', (chunk) => {
body.push(chunk);
});
res.on('end', () => {
try {
body = Buffer.concat(body);
body = body.toString();
if (body.startsWith('<!DOCTYPE html>')) {
_WARN_('Invalid request', 'There was a problem with your request. The parameter(s) you gave are missing or incorrect.');
} else if (body.startsWith('Throttled')) {
_WARN_('Throttled request', 'There was a problem with request limit.');
}
body = JSON.parse(body);
} catch (error) {
reject(error);
};
const returnObject = ReturnObject(
!(res.statusCode < 200 || res.statusCode >= 300),
res.statusMessage,
res.statusCode,
body
)
if (returnObject.code != 429) {
resolve(returnObject);
} else {
if (retries < maxRetries) {
console.log('retrying...');
return request(options, retries + 1);
} else {
console.log("Max retries reached. Bubbling the error up");
resolve(returnObject);
}
}
});
});
req.on('error', (error) => reject(error));
req.on('timeout', () => {
req.abort();
reject(new Error(`Lizard API request timed out. Current timeout is: ${Lizard.TIMEOUT} milliseconds`));
});
// End request
req.end();
});
}
return await request(options, 0);
};
}
I was trying to do this in a very difficult way. For anyone else that may stumble upon this here was my ultimate solution:
lizard.js:
async function request(path, params, retries = 0, maxRetries = 10) {
let options = await buildRequestOptions(path, params);
return new Promise((resolve, reject) => {
let req = https.request(options, (res) => {
let body = [];
// Set body on data
res.on('data', (chunk) => {
body.push(chunk);
});
// On end, end the Promise
res.on('end', async () => {
try {
body = Buffer.concat(body);
body = body.toString();
// Check if page is returned instead of JSON
if (body.startsWith('<!DOCTYPE html>')) {
_WARN_('Invalid request', 'There was a problem with your request. The parameter(s) you gave are missing or incorrect.');
} else if (body.startsWith('Throttled')) {
_WARN_('Throttled request', 'There was a problem with request limit.');
}
// Attempt to parse
body = JSON.parse(body);
} catch (error) {
reject(error);
};
if (res.statusCode == 429 && retries < maxRetries) {
const timeToWait = 60000 + (1000 * retries);
console.error('Throttled request ' + retries + ' time(s)');
console.log(`Retrying in ${timeToWait}ms...`);
setTimeout(() => {
resolve(request(path, params, retries + 1));
}, timeToWait);
} else {
resolve(
objectify(
!(res.statusCode < 200 || res.statusCode >= 300),
res.statusMessage,
res.statusCode,
body
)
);
}
});
});
// On error, reject the Promise
req.on('error', (error) => reject(error));
// On timeout, reject the Promise
req.on('timeout', () => {
req.abort();
reject(new Error(`Lizard API request timed out. Current timeout is: ${TIMEOUT} milliseconds`));
});
// End request
req.end();
});
};
I still resolve the object on fail as 429 is too many requests, so anything else needs to bubble up. On top of that if max retries is met, then if I see a 429 I know that I exceeded.

Recursive yield called from setTimeout

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:
Send 100 calls with Promise.allSettled([...]);
Some requests will succeed then process it;
Retry rejected requests after specified seconds.
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)
}
}
First iterations runs fine, from 100 items it fetches 30 and yields as expected;
Then setTimeout is working as expected;
Indicators functions runs for the second time;
The request works;
The first 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)
}
}

Retry Async/Await 3 times then display alert, alert not showing

I am trying to run my function 3 times if refreshAppData() returns false or causes an error. However, I cannot get the function to fire again after the initial call.
How can I retry my async / await and show an error after three attempts?
async function tryRefreshAppData(retries) {
const refreshOk = await refreshAppData();
if (!refreshOk && retries > 0) {
return tryRefreshAppData(retries - 1);
}
alert("refreshAppData was unsuccessful")
throw new Error("Failed to load the data correctly");
}
const MAX_NUMBER_OF_TRIES = 3;
const RETRIES = MAX_NUMBER_OF_TRIES - 1;
await tryRefreshAppData(RETRIES);
What you have done seems to already work but you should handle the case where refreshAppData() throw an error. I put an example below :
async function refreshAppData(retries) {
if (retries === 1) {
return Promise.resolve("ok");
}
return Promise.reject("tizzi");
}
async function tryRefreshAppData(retries) {
try {
const refreshOk = await refreshAppData(retries);
if (!refreshOk && retries > 0) {
return tryRefreshAppData(retries - 1);
}
console.log("SUCCESS", refreshOk);
return refreshOk;
} catch (e) {
console.log("ERROR");
if (retries > 0) {
return tryRefreshAppData(retries - 1);
}
throw new Error("Failed to load the data correctly");
}
}
const MAX_NUMBER_OF_TRIES = 3;
const RETRIES = MAX_NUMBER_OF_TRIES;
tryRefreshAppData(RETRIES);
Here's a quick solution (written in TS tho).
you can replace resolve() with reject() in order to test it of course:
const MAX_NUMBER_OF_TRIES = 3;
const RETRIES = MAX_NUMBER_OF_TRIES - 1;
const tryRefreshAppData: (iteration: number) => Promise<void> = (iteration) => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(iteration);
resolve();
}, 1000);
});
async function doAsyncCall() {
const iterations = Array.from({length: MAX_NUMBER_OF_TRIES}, (v,i) => i);
let failedAttempts = 0;
for(const iteration in iterations) {
try {
await tryRefreshAppData(parseInt(iteration));
break;
} catch {
failedAttempts +=1;
}
}
if(failedAttempts === MAX_NUMBER_OF_TRIES) {
alert("refreshAppData was unsuccessful");
}
}
doAsyncCall();
You can use the .catch() method on refreshAppData and return false:
function refreshAppData(retries) {
return new Promise((resolve, reject) => {
setTimeout( function() {
reject("Error!");
}, 750);
})
}
async function tryRefreshAppData(retries) {
console.log('retries', retries);
const refreshOk = await refreshAppData().catch(err => false);
if (!refreshOk && retries > 0) {
console.log('retry');
return tryRefreshAppData(retries - 1);
}
alert("refreshAppData was unsuccessful");
throw new Error("Failed to load the data correctly");
}
const MAX_NUMBER_OF_TRIES = 3;
const RETRIES = MAX_NUMBER_OF_TRIES - 1;
(async function() { await tryRefreshAppData(RETRIES); })()
You could also use try...catch and use the catch block to retry/show an error:
function refreshAppData(retries) {
return new Promise((resolve, reject) => {
setTimeout( function() {
reject("Error!");
}, 750);
})
}
async function tryRefreshAppData(retries) {
console.log('retries', retries);
try {
const refreshOk = await refreshAppData(retries);
} catch(err) {
if (retries > 0) {
console.log('retry');
return tryRefreshAppData(retries - 1);
}
alert("refreshAppData was unsuccessful")
throw new Error("Failed to load the data correctly");
}
}
const MAX_NUMBER_OF_TRIES = 3;
const RETRIES = MAX_NUMBER_OF_TRIES - 1;
(async function() { await tryRefreshAppData(RETRIES); })()

How can i use await, when it's don't recognize?

I'm trying to use await on var application = await SchedulerService().getIssues(issueId)
And it returns the error: SyntaxError: await is only valid in async function
I'm starting in node.js. What can I do to fix it?
I've tried already
Add async before initial function const SchedulerService = await function(){ at line 1
Add async on first return return async () => { where's return { at line 3
import schedulerConf from '../../config/scheduler';
import authConf from '../../config/auth';
import applicationConf from '../../config/application';
import request from 'request';
import schedule from 'node-schedule';
import dateformat from 'dateformat';
let interations = 0;
var serviceRecords = [];
var issueRecords = [];
const SchedulerService = function(){
return {
initialize: async () => {
console.log(`***** Starting Scheduler on ${dateformat(new Date(), "dd/mm/yyyy HH:MM:ss")}`);
var j = schedule.scheduleJob('*/1 * * * *', function(){
console.time('└─ Scheduler execution time');
if(interations === 0){
console.log(`Setting scheduler runtime to full time.`);
}else{
console.log(`------------------------`);
}
interations++;
console.log(`Job execution number: ${interations}.`);
SchedulerService().execute()
.then(response => {
console.log(`└─ Job ${interations} was successfully executed.`);
console.log(`└─ Execution date ${dateformat(new Date(), "dd/mm/yyyy HH:MM:ss")}`);
console.timeEnd('└─ Scheduler execution time');
}).catch(error => {
console.log(`└─ Job ${interations} returned error while executing.`);
});
});
},
execute: async () => {
return SchedulerService().getRecords2Sync()
.then(() => {
SchedulerService().sync().then(() => {
}).catch(error => {console.log({error})});
}).catch(error => {console.log({error})});
},
getRecords2Sync: async () => {
serviceRecords = [];
var options = {
url: `http://localhost:${authConf.serverPort}/application`,
method: 'GET',
headers: {
authorization: `${authConf.secret}`
}
}
return new Promise(function (resolve, reject) {
request(options, function (error, res, body) {
if (!error && res.statusCode == 200) {
const srs = JSON.parse(body);
const response = srs['response'];
for(let i =0;i < response.length;i++){
const { id, info } = response[i];
var status = "";
var issueId = "";
var statusClass = "";
for(let x = 0; x < info.length; x++){
if(info[x].key === "status"){
status = info[x].value;
statusClass = info[x].valueClass;
}
if(info[x].key === applicationConf.fields.issueId){
issueId = info[x].value;
}
}
if(statusClass === 0){
if(issueId !== null && issueId !== ""){
serviceRecords.push({id, status, issueId});
}
}
}
//console.log(serviceRecords);
resolve(serviceRecords);
} else {
//console.log(error);
reject(error);
}
});
});
},
getIssues : async (issueId) => {
issueRecords = [];
return new Promise(function(resolve, reject) {
var options = {
url: `http://localhost:${authConf.serverPort}/application2/${issueId}`,
method: 'GET',
headers: {
authorization: `${authConf.secret}`
}
}
request(options, function(error, res, body) {
if (!error && res.statusCode == 200) {
const issues = JSON.parse(body);
const { issue } = issues.response;
const { id, status, custom_fields } = issue;
issueRecords.push({id, status, custom_fields});
resolve(issueRecords);
} else {
reject(error);
}
});
});
},
sync : async () => {
return new Promise(function(resolve, reject) {
for (let i = 0; i < serviceRecords.length; i++) {
const application_id = serviceRecords[i].id;
const application_status = serviceRecords[i].status;
const application_issueId = serviceRecords[i].issueId;
//console.log('issueRecords.length: ', issueRecords);
//console.log('issueRecords.length: ', issueRecords.length);
console.log(`********** application`);
console.log(`└─ id ${application_id}`);
console.log(`└─ status ${application_status}`);
console.log(`└─ issueId ${application_issueId}`);
var application2 = await SchedulerService().getIssues(application_issueId)
.then(response => {
resolve(() => {
console.log(`i've found a record by issue_id ${application_issueId}`);
});
}).catch(error => {
reject(error);
});
}
});
}
}
}
export default new SchedulerService();
Thank you so much!
If you had getIssues resolve with issueId and issueRecords you might do something like this in sync:
sync: async () => {
// `map` over the serviceRecords and return a getIssues promise for each issueId
const promises = serviceRecords.map(({ issueId }) => SchedulerService().getIssues(issueId));
// Wait for all the promises to resolve
const data = await Promise.all(promises);
// Loop over the resolved promises and log the issueId
data.forEach((issueId, issueRecords) => console.log(issueId));
}

Handling http response one by one in Angular 6

Actually i have 15 http requests which are send to the API.
All i want to do is to handle responses one by one whithout waiting the end of all the requests (I have a request which can take some minutes to send result).
Service side :
findOneByOne(): Observable<any> {
const calls = this.getCardsPath().map(el => this.getPromises(el));
return Observable.forkJoin(calls)
.map(res => {
const tab = [];
for (let i = 0; i < res.length; i++) {
tab.push(this.checkInfoService(res[i].json()));
}
return tab;
});
}
getPromises(str: String): Promise<any> {
return this.requester.obtain({
restUrl: "/administration/" + str,
method: RequestMethod.Get
})
.toPromise()
.then(res => res)
.catch(err => err);
}
Component side :
displayDashboardInfoService() {
if (this.featuresFlag.getCurrentVersion() !== "1.08" && this.featuresFlag.getCurrentVersion() !== "-1") {
this.busy = this.dashboardInfoService.findAll()
.then((res: DashboardInfo[]) => this.findPaths(res))
.then((res: DashboardInfo[]) => this.loadItems(res))
.catch((err: any) => {
if (environment.debugLevel >= 3) console.error(err);
});
}
else {
this.dashboardInfoService.findOneByOne()
.subscribe((res) => {
const tab = [];
for (let i = 0; i < res.length; i++) {
tab.push(res[i][0]);
}
this.findPaths(tab);
this.loadItems(tab);
});
}
}
Thanks :)
A solution would be to change the forkJoin to merge so that instead of getting one event when all the requests are done you get an event after each one of them finishes.
If you'd have for example this:
waitForAll() {
this.values = [];
this.loadAllAtOnce([100, 200, 300, 400, 3000])
.subscribe(values => {
this.values = values;
});
}
loadAllAtOnce(values: number[]) {
return forkJoin(
values.map(x => of (x).pipe(delay(x)))
).pipe(
tap(values => {
console.log(values);
})
);
}
It could be rewritten to this:
asTheyCome() {
this.values = [];
this.loadAsSoonAsAvailable([100, 200, 300, 400, 3000])
.subscribe(value => {
this.values.push(value);
});
}
loadAsSoonAsAvailable(values: number[]) {
return merge(
...values.map(x => of (x).pipe(delay(x)))
).pipe(
tap(value => console.log(value))
);
}
You can find a working example here.

Categories

Resources