Async Funtion Return recursive call with setTimeout - javascript

I am calling a smart contract function to get some status which is the same as calling an API. And, I need to check if status.fulfilled===true before returning it to front-end. To do this I need to call the API every second to return the result as soon as possible. It usually takes 5-20 seconds for it to be fulfilled.
Here is how i tried to do it:
async function getStatus(requestId) {
try {
await Moralis.enableWeb3({ provider: 'metamask' });
const options = {
contractAddress: coinFlipAddress,
functionName: 'getStatus',
abi: coinFlipABI,
params: { requestId },
};
var status = await Moralis.executeFunction(options);
console.log(status);
if (status.fulfilled) {
console.log('fulfilled');
return status;
} else {
setTimeout(async () => {
return await getStatus(requestId);
}, 1000);
}
} catch (err) {
console.log(err);
return { error: err };
}
}
This keeps calling the getStatus function recursively until status.fulfilled===trueand console.log('fulfilled'); also logs when it is fulfilled, but it doesn't return it to where It is first initialized.
const handleFlip = async (choice) => {
setCurrentChoice(null);
setMetamaskInProgress(true);
const transaction = await flip(choice, amount);
setMetamaskInProgress(false);
setCurrentChoice(choices[choice]);
setFlipping(true);
setResult('Spinning');
const requestId = waitForConfirmation(transaction);
const result = await getStatus(requestId); //This is the initial call to getStatus()
console.log('RESULT ' + result);
if (result) {
setFlipping(false);
setSide(result.hasWon ? (choice === '0' ? 'Heads' : 'Tails') : choice === '0' ? 'Tails' : 'Heads');
setResult(result.hasWon ? 'You have won!' : 'You have lost :(');
}
};
What am I doing wrong? Also, could this recursive calls create any problems with memory? If yes, do you have any suggestions to handle this case differently?

You cannot return from a setTimeout callback. You'll need to promisify that, wait, and return afterwards:
async function getStatus(requestId) {
try {
await Moralis.enableWeb3({ provider: 'metamask' });
const options = {
contractAddress: coinFlipAddress,
functionName: 'getStatus',
abi: coinFlipABI,
params: { requestId },
};
var status = await Moralis.executeFunction(options);
console.log(status);
if (status.fulfilled) {
console.log('fulfilled');
return status;
} else {
await new Promise(resolve => {
setTimeout(resolve, 1000);
});
return getStatus(requestId);
}
} catch (err) {
console.log(err);
return { error: err };
}
}

I would have done something like this :
const MAX_RETRIES = 10; //maximum retries
const REQUEST_DELAY = 1000; //delay between requests (milliseconds)
// JS Implementation of the wide known `sleep` function
const sleep = (time) => new Promise(res => setTimeout(res, time, "done."));
/**
* Retrieve the status
* #param {string} requestId
*/
const getStatus = async (requestId) => {
try {
await Moralis.enableWeb3({ provider: 'metamask' }); //Guessing you need to call this only once
const options = {
contractAddress: coinFlipAddress,
functionName: 'getStatus',
abi: coinFlipABI,
params: { requestId },
};
let retries = 0;
while(retries < MAX_RETRIES) {
let status = await Moralis.executeFunction(options); // check status
console.log('attemtp %d | status %s', retries, status);
if (status.fulfilled) {
return status
}
await sleep(REQUEST_DELAY);
retries++;
}
throw new Error('Unable to retrieve status in time');
} catch (error) {
console.error('Error while fetching status', error);
throw error;
}
}
Few notes here :
I took the constants out of the function for more clarity,
use the while to loop for the number of retries.
Used a widely known 'sleep' method to create a delay between requests, and throw an error whenever something's not supposed to happen, happens (up to you to edit according to your needs).
Finally I used arrow functions for simplicity of use, know it's up to you ;)

You could try this change maxRetry and spaceBetweenRetry as you wish
async function getStatus(requestId) {
return new Promise(async (resolve, reject) => {
try {
let maxRetry = 10; //how many times you want to retry
let spaceBetweenRetry = 1000; // sleep between retries in ms
await Moralis.enableWeb3({ provider: 'metamask' }); //Guessing you need to call this only once
const options = {
contractAddress: coinFlipAddress,
functionName: 'getStatus',
abi: coinFlipABI,
params: { requestId },
};
for (let index = 0; index < maxRetry; index++) {
var status = await Moralis.executeFunction(options); // check status
console.log(status);
if (status.fulfilled) {
resolve(status) //return the promise if fullfilled.
}
await new Promise((r) => setTimeout(r, spaceBetweenRetry)); // sleep for spaceBetweenRetry ms
}
} catch (err) {
reject(err)
}
})}

Related

Second function not called asynchronously in nodejs

I am trying to call some function using a single express router , I want to call them in order, meaning that I don't want getLaps() function to execute before get streams function has done all the work , so I tried to use some solutions I found on the internet but it didn't work, the second function doesn't execute. Please help.
Here is my code :
router.get("/", async (req, res,done) => {
res.status(201).send('created user')
return getLaps(function () {
getStreams(function () {
});
});
// await getStreams();
// await getLaps();
// console.log("hey")
});
Here is the get laps function :
function getLaps(req) {
const access_token = '75f2d92fdc445033312854d775e039b6c5bf04e7';
//for test 3756582581,
const idL = [5567017025, 5566531480];
const stravaClient = StravaClientService.getClient(access_token);
const activityService = StravaActivityService(stravaClient);
var params = {
TableName: "run-id",
Key: {
"id": "15428785",
}
};
console.log("cool laps")
docClient.get(params, async function (err, data) {
if (err) {
console.log("Error", err);
} else {
}
idL.map((id, index) => setTimeout(() => activityService.listLaps(id), (5 + index) * 60)
)
//data.Item.json
});
}
and the streams function :
function getStreams(req) {
const idS = [
5567017025, 5566531480
];
const stravaClient = StravaClientService.getClient(access_token);
const activityService = StravaActivityService(stravaClient);
var params = {
TableName: "run-id",
Key: {
"id": "15428785",
}
};
console.log("cool streams")
docClient.get(params, async function (err, data) {
if (err) {
console.log("Error", err);
} else {
idS.map((id, index) => setTimeout(() => activityService.streamActivity(id), (5 + index) * 60))
console.log("got the streams")
}
});
}
in your getStream and getLaps function return promises instead of other object/Stuff like
async function getStream(){
return new Promise(async (resolve, reject){
//Do something
//where you want to return something just call resolve function like
resolve()
//if you want some output of getStream() just pass it to resolve function
//const result = 'I'm result'
resolve(result)
})
}
do same thing with the laps function and in your router call them with await keyword

can i make the async.retry method retry even on successfull queries but based on a condition

I'm studying the node.js module async,I want to find out if there is a way to change the async.retry method to retry even on successfull operations but stop based on some condition or response let's say its an api call.
According to its docs ,the function will continue trying the task on failures until it succeeds.if it succeeds it will only run only that time But how can i make it work the same on successfull operations and make it stop on some condition ?
const async = require('async');
const axios = require('axios');
const api = async () => {
const uri = 'https://jsonplaceholder.typicode.com/todos/1';
try {
const results = await axios.get(uri);
return results.data;
} catch (error) {
throw error;
}
};
const retryPolicy = async (apiMethod) => {
async.retry({ times: 3, interval: 200 }, apiMethod, function (err, result) {
// should retry untill the condition is met
if (result.data.userId == 5) {
// stop retring
}
});
};
retryPolicy(api);
Yes, You can just throw a custom error if condition is not met. Would be something like that:
const async = require('async');
const axios = require('axios');
const api = async () => {
const uri = 'https://jsonplaceholder.typicode.com/todos/1';
try {
const results = await axios.get(uri);
if(typeof result.data.userId != 'undefined' && result.data.userId == 5){ // change this condition to fit your needs
return results.data;
}else{
throw {name : "BadDataError", message : "I don't like the data I got"};
}
} catch (error) {
throw error;
}
};
I don't think this is possible.
On the async.retry documentation you can find this description:
Attempts to get a successful response from task no more than times
times before returning an error. If the task is successful, the
callback will be passed the result of the successful task. If all
attempts fail, the callback will be passed the error and result (if
any) of the final attempt.
However, using the delay function given here, you can do what you want another way:
const async = require('async');
const axios = require('axios');
const delay = (t, val) => {
return new Promise((resolve) => {
setTimeout(() => { resolve(val) }, t);
});
}
const api = async () => {
const uri = 'https://jsonplaceholder.typicode.com/todos/1';
try {
const results = await axios.get(uri);
return results.data;
} catch (error) {
throw error;
}
};
const retryPolicy = async (apiMethod) => {
const times = 3
const interval = 200
let data
for (count = 0; count < 3; count++) {
try {
data = await apiMethod()
catch(e) {
console.log(e)
await delay(interval)
continue
}
if (data.userId === 5) {
break;
}
await delay(interval)
}
// do something
};
retryPolicy(api);

JavaScript - replace setTimeout with async / await

First, I know this is a common question. I'm trying to get a handle on how to use async / await in place of setTimeouts, but all the examples I see online use a setTimeout to simulate the async. This throws me off when it's a set timeout that I'm trying to replace.
In the function below, I want this.filteredResultsto await the results of an API call before trying to filter those results and assign it to this.filteredResults.
getResults() {
let allResults= airtableQuery.getTable("Transfers"); // API call using imported 'getTable' function
console.log(allResults); // returns full array ▶[] although it's not available for filtering yet.
setTimeout(() => { // I want to replace this timeout
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
}, 250); // random ms that is roughly how long airtableQuery takes for the API call.
},
And the airtableQuery:
getTable(table) {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
},
function done(err) {
if (err) {
this.$toasted.error(err);
}
}
);
return recordsArr;
},
Please make the outer function an async function and then await the results before filtering them.
async function getResults() {
let allResults = await airtableQuery.getTable("Transfers");
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
},
Given that getTable() is not a Promise, await will not do anything. For that reason, we can make getTable() return a Promise which will resolve with recordsArr.
getTable(table) {
return new Promise((resolve, reject) => {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
},
function done(err) {
if (err) {
this.$toasted.error(err);
reject(err)
}else {
resolve(recordsArr)
}
}
);
})
}
Hope it helps.
i always likes primise,this my code show you
getTable(table) {
return new Promise((res, rej) => {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
res(recordsArr)
},
function done(err) {
if (err) {
this.$toasted.error(err);
rej(err)
}
}
);
})
}
getResults() {
airtableQuery.getTable("Transfers").then(res => {
let allResults = res
console.log(allResults);
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
});
}

async await retry log after all retries

How can I console.log if it still fails after all retries done? and console.log if it succeeds, using async-retry package:
const retry = require('async-retry');
async function updateDB(updateUser) {
await retry(async () => {
const res = await updateUser;
if (/* some error after finishing all retries*/) {
console.log('failed');
}
console.log('Success');
}, {
retries: 5
});
}
how can this be achieved?
or in other words, how can I call another function (function A) only after all retries attempts failed? and call function B if it didn't throw at all.
const retry = require('async-retry');
async function updateDB(updateUser) {
try{
const result = await retry(async () => {
const res = await Promise.all(updateUser).then(()=>{
try{
return new Promise((resolve) => resolve('OK')), { retries: 5 }
}catch(e){
return new Promise((resolve) => resolve('KO')), { retries: 5 };
}
});
}
);
}catch(err){
console.log('The function execution failed !')
}
}
you can use function onRetry, something like this
const retry = require('async-retry');
async function updateDB(updateUser) {
await retry(async () => {
const res = await test();
console.log('Success');
}, {
onRetry: (err, number) => {
console.log('attempt', number)
if (number === 5) {
console.log(err)
}
},
retries: 5
});
}
Have you tried something like:
const result = await retry(async () => {
const res = await updateUser;
if (/* some error after finishing all retries*/) {
console.log('failed');
}
console.log('Success');
return 'success';
}, {
retries: 5
});
if (result != 'success') {
console.log('all failed');
}

Can not return from a function

I have a function that looks like following
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
return true;
} else {
console.log("studio not available");
return false;
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
};
however the return statements don't return anything when I use the function in the following manner
useEffect(() => {
const agent = checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("studio available is: ", agent);
}, []);
the console.log massages appear but the return statement is undefined.
any help would be appreciated.
You can not return from a callback function, as it is running asynchronously and you are not waiting for it to have a result ready.
You can however make the function itself async by returning a Promise instead of the actual result and wait until the Promise has a result ready (e.g. it is resolved):
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
reject(); // reject on failure
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
resolve(true); // resolve instead of return
} else {
console.log("studio not available");
resolve(false);
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
).then((agent) => { // then callback is called when the promise resolved
console.log("studio available is: ", agent);
}).catch(error => { // catch is called when promise got rejected
console.log('An error happened');
});
}, []);
The function servceInfo.OnServiceStateChange is a function into the object (seems to be an event).
I'd suggest declaring a variable on the checkForAvailableAgent like connected and change it's value when the event is called.
Then access it using checkForAvailableAgent.connected.
A version with async/await and try/catch
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId,
OnError: reject,
OnServiceStateChange: e => resolve(e.ConnectedAdvisers > 0)
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
(async () => {
try {
const isAvailable = await checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("Result", isAvailable)
} catch(e) {
console.error(e)
}
})()
// console.log("studio available is: ", agent);
}, []);
There are 2 possible reasons
you are not returning anything from checkForAvailableAgent.
After returning from the checkForAvailableAgent, it might be asynchronous function. You can use async & await.

Categories