Exponential Backoff not returning data in promise - javascript

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.

Related

JavaScript - result undefined - forcing one method to complete before another?

I have the following NodeJS code running in an AWS Lambda:
const https = require('https');
exports.handler = async event => {
try {
const currentWeekNumber = getWeekNumber(new Date(Date.now()));
const currentTournamentId = await getCurrentTournamentIdByWeekNumber(currentWeekNumber);
// note: Issue is currentTournamentId is returned as undefined from above, so below method fails
const leaderBoard = await getLeaderboardByTournId(currentTournamentId);
return {
statusCode: 200,
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(leaderBoard),
};
} catch (error) {
console.log('Error: ️', error);
return {
statusCode: 400,
body: error.message,
};
}
};
function getCurrentTournamentIdByWeekNumber(currentWeekNumber) {
const promise = new Promise((resolve, reject) => {
const options = {
host: 'MY_HOST',
path: '/MY_PATH',
headers: {
'key': 'value'
}
};
const req = https.get(options, res => {
let rawData = '';
res.on('data', chunk => {
rawData += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(rawData));
} catch (err) {
reject(new Error(err));
}
});
});
req.on('error', err => {
reject(new Error(err));
});
});
promise.then(result => {
for (let i = 0; i <result.schedule.length; i++) {
let weekNumber = result.schedule[i].date.weekNumber;
if(currentWeekNumber == weekNumber) {
return result.schedule[i].tournId;
}
}
});
}
function getLeaderboardByTournId(tournId) {
return new Promise((resolve, reject) => {
const options = {
host: 'MY_HOST',
path: '/MY_PATH',
headers: {
'key': 'value'
}
};
const req = https.get(options, res => {
let rawData = '';
res.on('data', chunk => {
rawData += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(rawData));
} catch (err) {
reject(new Error(err));
}
});
});
req.on('error', err => {
reject(new Error(err));
});
});
}
function getWeekNumber(d) {
// Copy date so don't modify original
d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
// Set to nearest Thursday: current date + 4 - current day number
// Make Sunday's day number 7
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay()||7));
// Get first day of year
var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
// Calculate full weeks to nearest Thursday
var weekNo = Math.ceil(( ( (d - yearStart) / 86400000) + 1)/7);
// Return array of year and week number
return weekNo;
}
I am having an issue where currentTournamentId is being passed into getLeaderboardByTournId as undefined, I think this is due to the execution order of my functions.
I know that the logic is correct in all the functions.
How can I ensure that getCurrentTournamentIdByWeekNumber completes and returns a result before getLeaderboardByTournId is called?
This is my first attempt at node lambda and only getting to grips with JS execution order!
You are correctly awaiting your getCurrentTournamentIdByWeekNumber method, however inside of that method, you're not returning anything so there's no promise to wait for.
If you're add a return it will probably work:
return promise.then(result => {
...

problem awaiting the callback given to a function

I have a function who send a message to the server to get the answer and if the answer is true I want my application to send an error to the user. The problem is that I can't manage to await the callback in the Fetch function I wrote.
This is the function who send the question to the server.
async donglePaired(){
if (Platform.OS !=='ios'){
var pairedDevices = await BluetoothScanner.getPairedDevices();
console.log('Sending........');
let data={
data:pairedDevices,
};
new Api().fetch("bluetoothCheck",{devices:JSON.stringify(data),userid:this.state.probe.UID},(result) => {
if (!result.err) return false;
console.log("Dongle already paired");
return true;
//logNetworkState
});
}
}
This is the Api.fetch function i wrote
fetch(action,data,cb){
let url=Config.URL+"?version="+Config.VERSION+"&action="+action;
let post="";
let formData=new FormData();
for(let k in data) formData.append(k,data[k]);
for(let k in data) post+="&"+k+"="+encodeURIComponent(data[k]).replace(/%20/g,'+');
console.log(url+post);
console.log(url);
if (data.batch) console.log(data.batch);
let sending=true;
fetch(url,{
method: 'post',
body: formData
})
.then(function(response){
if (true) return response.json();
let txt=response.text();
console.log(txt);
return JSON.parse(txt);
})
.then(
(result)=>{
if (!sending) return;
sending=false;
console.log(JSON.stringify(result));
if (cb) cb(result);
},
(error)=>{
if (!sending) return;
sending=false;
console.log("fetch error");
console.log(error);
if (cb) cb();
}
);
setTimeout(()=>{
console.log("http timeout")
if (!sending) return console.log("nothing to abort");
if (cb) cb();
},Config.HTTP_TIMEOUT*1000)
}
}
And this is my main code where I wait for the first function donglePaired, and if donglePaired return true I send an error to the user.
let donglePaired = await this.props.app.donglePaired();
if (donglePaired) return this.props.app.setError("ERR_DONGLE");
The problem is that the program doesnt wait for donglePaired, despite of the await
your code here is inappropriate
let donglePaired = await this.props.app.donglePaired();
if (donglePaired) return this.props.app.setError("ERR_DONGLE");
Async function cannot return value normally unless it is a Promise
See my simple demo below!
async function test() {
const result = await asyncRequest()
return result
}
function asyncRequest() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000)
})
}
test().then((data) => {
console.log(data)
})
The snippets should give you an idea how to await the callback
Sending to the API
async function remove_configuration(filename) {
const data = { filename };
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
};
await fetch('/delete', options);
}
Just Retrieving Data
async function display() {
let response = await fetch('/get-available-configurations')
let data = await response.json(); // JSON or Text what do you prefer
// do something with data
}
You could return an Promise.race() with your timeout function.
fetch(action, data, cb) {
let url = Config.URL + "?version=" + Config.VERSION + "&action=" + action;
let post = "";
let formData = new FormData();
for (let k in data) formData.append(k, data[k]);
for (let k in data)
post += "&" + k + "=" + encodeURIComponent(data[k]).replace(/%20/g, "+");
console.log(url + post);
console.log(url);
if (data.batch) console.log(data.batch);
let sending = true;
return Promise.race([
fetch(url, {
method: "post",
body: formData
})
.then(res => res.json())
.then(result => {
if (!sending) return;
sending = false;
return result;
}),
sleep(Config.HTTP_TIMEOUT * 1000)
]);
}
const sleep = ms => new Promise((_, rej) => setTimeout(rej("TIMEOUT"), ms));
It either returns you the value or it rejects with TIMEOUT or it rejects with an error from fetch
And donglePaired looks like this then. I have wrapped it with an try / catch
async donglePaired() {
if (Platform.OS !== "ios") {
var pairedDevices = await BluetoothScanner.getPairedDevices();
console.log("Sending........");
let data = {
data: pairedDevices
};
try {
let result = await new Api().fetch("bluetoothCheck", {
devices: JSON.stringify(data),
userid: this.state.probe.UID
});
if (!result.err) return false;
console.log("Dongle already paired");
return true;
//logNetworkState
} catch (err) {
console.log(err);
}
}
}
One possibility is to drop the async and change it to this:
donglePaired() {
return new Promise( function(resolve, reject) {
if (Platform.OS !=='ios'){
var pairedDevices = await BluetoothScanner.getPairedDevices();
console.log('Sending........');
let data={
data:pairedDevices,
};
new Api().fetch("bluetoothCheck",{devices:JSON.stringify(data),userid:this.state.probe.UID},(result) => {
if (!result.err) reject(false);
console.log("Dongle already paired");
resolve(true);
//logNetworkState
});
}
reject(false);
});
}
And:
this.props.app.donglePaired().then( (response) => {
// do something here, this will only run if the response is true
});

Azure IoT Edge node SDK invokeDeviceMethod not working asynchronously

I am trying to return a result for a direct method call in an async fashion.
I tried:
var client = Client.fromConnectionString(process.env.AZ_IOT_CONNECTION_STRING);
var methodParams = {
methodName: "method",
payload: 10, // Number of seconds.
responseTimeoutInSeconds: 60
};
// Call the direct method on your device using the defined parameters.
client.invokeDeviceMethod(
req.params.deviceId,
methodParams,
(err, result) => {
if (err) {
console.error(err);
} else {
console.log("success");
}
}
);
On Device:
const method = async (request, response) => {
const longProcess = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 5000);
});
};
try {
await longProcess();
response.send(200, `success`);
} catch (error) {
response.send(500, `Error: ${error}:`);
}
};
client.onDeviceMethod("method", method);
Expected: Returns success after 5 seconds
Actual: returns "BadRequest" "errorCode :400027" but the method is executed correctly.
please work with the promise function instead of the callback like so:
async deviceSyncInvoke(deviceID: string, methodName: string, data:any, responseTimeoutInSeconds?: number): Promise<[number, any]> {
try{
if (!this.client){
await this.openClient();
}
const methodParam: DeviceMethodParams = {
methodName: methodName,
payload: JSON.stringify(data)
}
// The minimum and maximum values for responseTimeoutInSeconds are 5 and 300 seconds.
//If timeout is not provided, it the default value of 30 seconds is used
if (responseTimeoutInSeconds && responseTimeoutInSeconds !== undefined) {
methodParam.responseTimeoutInSeconds = responseTimeoutInSeconds;
}
const res: ResultWithIncomingMessage<any> = await this.client.invokeDeviceMethod(deviceID, methodParam);
if (res.message.statusCode == undefined || res.message.statusCode > 299) {
throw new Error(`statusCode: ` + res.message.statusCode + `statusMessage: ` + res.message.statusMessage);
}
return [2001, "message invoking command with res: " + res];
}catch(err) {
return [5000, "error deviceSyncInvoke: " + err];
}
}

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));
}

How to do retry on status 200 using axios-retry

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 });
}

Categories

Resources