I found an exponential backoff function which is supossed to retry an API fetch in case of an error in exponential delays:
async function exponentialBackoff(toTry, max, delay) {
console.log('max', max, 'next delay', delay);
try {
let response = await toTry()
return response;
} catch (e) {
if (max > 0) {
setTimeout(function () {
exponentialBackoff(toTry, --max, delay * 2);
}, delay);
} else {
console.log('maximum amount of tries exceeded', e);
return e;
}
}
}
I then use the function with the help of GA API library to make a request. On purpose I am sending an invalid body to make the request fail:
response = await exponentialBackoff(async function () {
return await gapi.client.request({
path: 'https://analyticsadmin.googleapis.com/v1beta/properties',
method: 'POST',
body: property
})
}, 10, 30)
console.log("NEXT LINE");
What I would expect is that the exponential backoff will run out of all attempts (10) and only after that it would execute the console log.
What actually happens is that the console log gets executed right after first try. What am I doing wrong there?
Thank you
Promisify setTimeout and await it. A promisified variant is
function sleep(ms) { return new Promise(res => { setTimeout(res, ms); }); }
await exponentialBackoff in the catch block.
The fixed code is:
function sleep(ms) { return new Promise(res => { setTimeout(res, ms); }); }
async function exponentialBackoff(toTry, max, delay) {
console.log('max', max, 'next delay', delay);
try {
let response = await toTry()
return response;
} catch (e) {
if (max > 0) {
await sleep(delay);
return await exponentialBackoff(toTry, --max, delay * 2);
} else {
console.log('maximum amount of tries exceeded', e);
return e;
}
}
}
Related
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)
}
})}
function sleep(t) {
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('timeout!')
return resolve({isTimeout: true})
}, t);
});
}
function thirdPartyFunction(t) { // thirdPartyFunction can't be edited
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('thirdPartyFunction completed!')
return resolve({success: true})
}, t);
});
}
function main() {
return new Promise(async(resolve, reject) => {
try {
let thirdPartyFunctionExecutionTime = Math.floor(Math.random() * 10) + 1;
thirdPartyFunction(thirdPartyFunctionExecutionTime * 1000, false).then( (r) => {
console.log('should not execute this if thirdPartyFunctionExecutionTime > timeout') // only goal
// other code which is not useful after timeout
});
const timeout = 3;
console.log(`thirdPartyFunctionExecutionTime: ${thirdPartyFunctionExecutionTime}, timeout - ${timeout}`)
await sleep(timeout * 1000, true);
throw 'stop main()'
// return
} catch (error) {
console.log('in catch')
return;
}
})
}
main()
Timeout is fixed. thirdPartyFunctionExecutionTime might be very large (sometimes) in my actual case, say 30 secs. I don't want something to be running on background after timeout.
thirdPartyFunction promise function should stop execution on timeout.
FYI, here's a generic function to add a timeout with the option of cancelling. This can be used with any promise to add a timeout to it.
If the underlying asynchronous operation represented by the promise is cancellable, then you can supply a cancel function. If the async operation finishes first, this will clean up the timer for you automatically so it doesn't keep running.
// the main purpose of this class is so that callers can test
// to see if the reject reason is a TimeoutError vs. some other type of error
class TimeoutError extends Error {
constructor(...args) {
super(...args);
}
}
// add a timeout to any promise
function addTimeout(promise, t, timeoutMsg = "timeout") {
let timer;
const timerPromise = new Promise((resolve, reject) => {
timer = setTimeout(() => {
timer = null;
// if the promise has a .cancel() method, then call it
if (typeof promise.cancel === "function") {
try {
promise.cancel();
} catch(e) {
console.log(e);
}
}
reject(new TimeoutError(timeoutMsg));
}, t)
});
// make sure the timer doesn't keep running if the promise finished first
promise.finally(() => {
if (timer) {
clearTimeout(timer);
}
})
return Promise.race([promise, timerPromise]);
}
And, you could then use this in your code like this:
function main() {
addTimeout(thirdPartyFunction(...), 3000).then(result => {
// thirdPartyFunction succeeded and timeout was not hit
// use the result here
}).catch(err => {
console.log(err);
// an error occurred here
if (err instanceof TimeoutError) {
// timeout
} else {
// other error
}
})
}
If your asynchronous operation has an ability to be cancelled if this operation times out, you could support that like this:
function main() {
let p = thirdPartyFunction(...);
p.cancel = function () {
// cancel the thirdPartyFunction here (depends uponn the specific operation)
}
addTimeout(p, 3000).then(result => {
// thirdPartyFunction succeeded and timeout was not hit
// use the result here
}).catch(err => {
console.log(err);
// an error occurred here
if (err instanceof TimeoutError) {
// timeout
} else {
// other error
}
})
}
I have chained requests on GA API, meaning the second requests uses result from the first request. I wanted to use exponentialBackoff() function to retry if there is an error (status 429 for example).
My question is, what would be the best way of checking that both of the requests were successful and only repeat the one which wasn't. In my example, both of the requests get called again even if only the second one is not successful.
async function exponentialBackoff(toTry, max, delay) {
console.log('max', max, 'next delay', delay);
try {
let response = await toTry()
console.log(response);
} catch (e) {
if (max > 0) {
setTimeout(function () {
exponentialBackoff(toTry, --max, delay * 2);
}, delay);
} else {
console.log('we give up');
}
}
}
propertyArr.forEach(async function (e, i, a) {
await exponentialBackoff(async function () {
let response = await gapi.client.request({
path: 'https://analyticsadmin.googleapis.com/v1beta/properties',
method: 'POST',
body: e
})
return await gapi.client.request({
path: 'https://analyticsadmin.googleapis.com/v1beta/' + response.result.name + '/dataStreams',
body: dataStreamArr[i],
method: 'POST'
})
}, 10, 30)
})
This question already has answers here:
Timeout in async/await
(3 answers)
Closed 1 year ago.
In my express application, I am making call to 2 APIs. The 2nd API is managed by 3rd party and sometimes can take more than 5 seconds to respond. Hence, I want to just wait for 1 second for the API to respond. If it does not, just proceed with data from 1st API.
Below is the mock-up of the functions being called.
I am thinking to use setTimeout to throw error if the API takes more than 1 second. If the API responds within 1 second then I just cancel the setTimeout and no error is ever thrown.
But there is problem with this approach:
setTimeout errors cannot be catched using try...catch block.
I cannot use axios's timeout option, as I still need to wait for the 2nd API to finish the processing and save the data in the DB. This will ofcourse, can happen later, when the 2nd API call finishes.
// Function to simulate it's taking time.
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Track whether it took time.
let isTimeOut = false
async function test() {
console.log('starting')
try {
const one = await apiCall1()
const myt = setTimeout(() => {
console.log('Its taking time, skip the 2nd API Call')
isTimeOut = true
throw new Error('Its taking time')
})
const two = await apiCall2(myt)
} catch (error) {
console.log(error)
}
saveInDB({ ...one, ...two })
}
async function apiCall2(timeOutInstance) {
console.log('start-apiCall')
await cWait(1800)
clearTimeout(timeOutInstance)
if (isTimeOut) saveInDB()
console.log('done-apiCall')
}
async function apiCall1() {
await cWait(5)
}
async function saveInDB(data) {
console.log('saveInDB')
}
test()
please note, this is not the answer as it was when it was accepted
as I misread the question and failed to call saveInDB in a timed out
situation
Promise.race seems perfect for the job
Also, you'd actually use your cWait function, not for mock-up, but to actually do something useful ... win the race :p
const api2delay = 800;
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const TIMEDOUT = Symbol('TIMEDOUT');
async function cReject(ms) {
return new Promise((_, reject) => setTimeout(reject, ms, TIMEDOUT));
}
function apiCall2timeout(timeoutCallback) {
const resultApi2 = apiCall2();
const timeout = cReject(1000);
return Promise.race([resultApi2, timeout])
.catch(e => {
if (e === TIMEDOUT) {
resultApi2.then(timeoutCallback);
} else {
throw e;
}
});
}
async function test() {
console.log('starting')
let one, two;
try {
one = await apiCall1();
two = await apiCall2timeout(saveInDB);
} catch (error) {
console.log('error', error)
}
saveInDB({
...one,
...two
})
}
async function apiCall2() {
console.log('start-apiCall2')
await cWait(api2delay)
console.log('done-apiCall2')
return {
api2: 'done'
}
}
async function apiCall1() {
await cWait(5)
return {
api1: 'done'
}
}
async function saveInDB(data) {
console.log('saveInDB', data)
}
test()
Note: I changed where one and two were declared since const is block scoped
I you run with await cWait(800) in apiCall2, the saveInDB will run with both data.
But if you run await cWait(1800), the saveInDB will run 2 times.
// Function to simulate it's taking time.
async function cWait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// https://italonascimento.github.io/applying-a-timeout-to-your-promises/
const promiseTimeout = function (ms, promise) {
// Create a promise that rejects in <ms> milliseconds
let timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('Timed out in ' + ms + 'ms.')
}, ms)
})
// Returns a race between our timeout and the passed in promise
return Promise.race([
promise,
timeout
])
}
// Track whether it took time.
let isTimeOut = false
async function test() {
console.log('starting')
const one = await apiCall1() // get data from 1st API
let two = {};
try {
two = await promiseTimeout(1000, apiCall2())
} catch (error) {
isTimeOut = true;
console.log(error)
}
saveInDB({ ...one, ...two })
}
async function apiCall2() {
console.log('start-apiCall')
await cWait(800)
console.log('done-apiCall', isTimeOut)
if (isTimeOut) {
saveInDB({ 2: 'two' })
}
return { 2: 'two' }
}
async function apiCall1() {
await cWait(5)
return { 1: 'one' }
}
async function saveInDB(data) {
console.log('saveInDB', data)
}
test()
I can see in Chrome task manager that the tab in which following code is running eats more and more memory, and it is not released until the promise is resolved
UPDATE
Main idea here is to use a single 'low level' method which would handle "busy" responses from the server. Other methods just pass url path with request data to it and awaiting for a valuable response.
Some anti-patterns was removed.
var counter = 1
// emulates post requests sent with ... axios
async function post (path, data) {
let response = (counter++ < 1000) ? { busy: true } : { balance: 3000 }
return Promise.resolve(response)
}
async function _call (path, data, resolve) {
let response = await post()
if (response.busy) {
setTimeout(() => {
_call(path, data, resolve)
}, 10)
throw new Error('busy')
}
resolve(response.balance)
}
async function makePayment (amount) {
return new Promise((resolve, reject) => {
_call('/payment/create', {amount}, resolve)
})
}
async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}
makePayment(500)
.then(() => {
getBalance()
.then(balance => console.log('balance: ', balance))
.catch(e => console.error('some err: ', e))
})
The first time you call _call() in here:
async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}
It will not call the resolve callback and it will return a rejected promise and thus the new Promise() you have in getBalance() will just do nothing initially. Remember, since _call is marked async, when you throw, that is caught and turned into a rejected promise.
When the timer fires, it will call resolve() and that will resolve the getBalance() promise, but it will not have a value and thus you don't get your balance. By the time you do eventually call resolve(response.balance), you've already called that resolve() function so the promise it belongs to is latched and won't change its value.
As others have said, there are all sorts of things wrong with this code (lots of anti-patterns). Here's a simplified version that works when I run it in node.js or in the snippet here in the answer:
function delay(t, val) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, val), t);
});
}
var counter = 1;
function post() {
console.log(`counter = ${counter}`);
// modified counter value to 100 for demo purposes here
return (counter++ < 100) ? { busy: true } : { balance: 3000 };
}
function getBalance () {
async function _call() {
let response = post();
if (response.busy) {
// delay, then chain next call
await delay(10);
return _call();
} else {
return response.balance;
}
}
// start the whole process
return _call();
}
getBalance()
.then(balance => console.log('balance: ', balance))
.catch(e => console.error('some err: ', e))