Nodejs - Retry same function on error callback in production - javascript

I have a javascript function with setTimeOut and I am retrying to call same function, if any error from API call.I am calling same function in catch block.Is my node server is going crash and resources will be blocked or it will keep calling getData() function
let retry = ()=> {
setTimeout(() => {
getData()
retry()
}, 3000);
}
let getData = () =>{
Someapi.getData().then((token) => {
console.log(`Data after 3 seconds->${token}`)
}).catch((err) => {
getData()
})
}

I do not know if this work.
let retry = () => {
setTimeout(() => {
getData();
retry();
}, 3000);
};
while (true) {
let getData = () => {
Someapi.getData()
.then(token => {
console.log(`Data after 3 seconds->${token}`);
return false;
})
.catch(err => {
return true;
});
};
}

I use this retry code in my project, it works well in production:
const pause = (duration) => {
return new Promise(resolve => setTimeout(resolve, duration));
};
const retry = (retryTimes, func, delay) => {
return func().catch(
(err) => {
if(retryTimes > 0) {
return pause(delay).then(
() => retry(retryTimes - 1, func, delay * 2)
);
} else {
return Promise.reject(err);
}
}
);
};

Related

Cleanup setTimeout inside useffect async function

I'm developing an autosave component in React.
My logic is whenever the "bestOf" changes, I check with an async fetch function if the data is equal to the data that is stored in my database.
If it is equal, I set my state to saved
If it is not equal, I set my state to unsaved and then I want to wait 5 seconds before calling my handleSave function
I am now trying to cleanup this timeout in order to not call this function multiple times if multiple modifications to the bestOf occurs but the useEffect cleanup function doesn't work in my case.
Here is my try with clearTimeout
useEffect(() => {
let timeout;
const compareSave = async () => {
await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`)
.then((res) => res.json())
.then((data) => {
if (JSON.stringify(data) === JSON.stringify(bestOf)) {
return setSave("saved");
} else {
setSave("unsaved");
timeout = setTimeout(() => {
handleSave();
}, 5000);
}
})
.catch((err) => console.log(err));
};
compareSave();
return () => {
clearTimeout(timeout);
};
}, [bestOf]);
And here is another try with AbortController
useEffect(() => {
const controller = new AbortController();
const compareSave = async () => {
await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`, {
signal: controller.signal,
})
.then((res) => res.json())
.then((data) => {
if (JSON.stringify(data) === JSON.stringify(bestOf)) {
return setSave("saved");
} else {
setSave("unsaved");
setTimeout(() => {
handleSave();
}, 5000);
}
})
.catch((err) => console.log(err));
};
compareSave();
return () => {
controller.abort()
};
}, [bestOf]);
Both solutions doesn't work and the timeout function is executed multiple time for each modification. Any suggestions ?
After a lot of tries i finally found that I need to add a flag to ignore the stuff after fetch in the timeout for if bestOf changes.
Otherwise because it is async, the timeout would be set after I have cleared it.
useEffect(() => {
let shouldIgnore = false;
let timeout;
const compareSave = async () => {
await fetch(`/api/bestof/get?bestof_id=${bestOf.id}`)
.then((res) => res.json())
.then((data) => {
if (shouldIgnore) {
return;
}
if (JSON.stringify(data) === JSON.stringify(bestOf)) {
return setSave('saved');
} else {
setSave('unsaved');
timeout = setTimeout(() => {
handleSave();
}, 5000);
}
})
.catch((err) => console.log(err));
};
compareSave();
return () => {
shouldIgnore = true;
clearTimeout(timeout);
};
}, [bestOf]);

cancel multiple promises inside a promise on unmount?

hi i want to cancel promise on unmount since i received warning,
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
My code:
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
(val) => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
(error) => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
useEffect(() => {
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split('toolbox-talks/');
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log('resolve 1');
setFileUri(filesystemUri);
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log('resolve 2');
setFileUri(imageObject.uri);
}
// otherwise download to cache
} catch (err) {
console.log('error 3');
setFileUri(uri);
}
};
const cancelable = makeCancelable(initialize());
cancelable.promise
.then(() => {
console.log('reslved');
})
.catch((e) => {
console.log('e ', e);
});
return () => {
cancelable.cancel();
};
}, []);
but i still get warning on fast press, help me please?
You're cancelling the promise, but you are not cancelling the axios call or any of the logic that happens after it inside initialize(). So while it is true that the console won't print resolved, setFileUri will be called regardless, which causes your problem.
A solution could look like this (untested):
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
error => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
}
};
};
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split("toolbox-talks/");
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log("resolve 1");
return filesystemUri;
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log("resolve 2");
return imageObject.uri;
}
// otherwise download to cache
} catch (err) {
console.error("error 3", err);
return uri;
}
};
useEffect(() => {
const cancelable = makeCancelable(initialize());
cancelable.promise.then(
fileURI => {
console.log("resolved");
setFileUri(fileURI);
},
() => {
// Your logic is such that it's only possible to get here if the promise is cancelled
console.log("cancelled");
}
);
return () => {
cancelable.cancel();
};
}, []);
This ensures that you will only call setFileUri if the promise is not cancelled (I did not check the logic of makeCancelable).

Use jest for testing timeouts calling recursive function

I want to test the following code:
const poll = (maxTries, interval, channel, stopTime) => {
let reached = 1;
const someInformation = someGetter();
const fetchData = async (resolve, reject) => {
const data = await networkClass.someApiCall();
if (data.stopTime === 1581516005) {
console.log("cond true");
someInformation.meta1 = transFormer(someInformation);
someInformation.meta2 = transFormer(someInformation);
someInformation.meta3 = {
...someInformation.meta1,
data,
};
resolve(someInformation);
} else if (reached < maxTries) {
reached += 1;
console.log("do it again");
setTimeout(fetchData, interval, resolve, reject);
} else {
reject(new Error('max retries reached'));
}
};
return new Promise(fetchData);
};
const checkForUpdates = () => {
setTimeout(() => {
poll(/* max retries */ 10, /* polling interval */ 1000, channel, stopTime)
.then((res) => {
setData(res);
console.log({ res: res.meta3.data });
})
.catch((e) => console.log({ e }));
}, 20000);
};
The test looks like that:
it(`should do stuff`, () => {
jest.spyOn(networkClass, 'someApiCall')
.mockResolvedValueOnce({ stopTime })
.mockResolvedValueOnce({ stopTime })
.mockResolvedValueOnce({ stopTime: 1581516005 });
checkForUpdates();
jest.advanceTimersByTime(40000);
expect(setDataMock).toHaveBeenCalled();
});
That console.log (console.log("do it again");) is only printed once, as if the test would not be able to call a setTimeout within a setTimeout. Do you have any ideas what might help?

How to set a time limit to a method in NodeJs?

I have a use case, where I am doing an external API call from my code,
The response of the external API is required by my code further on
I am bumping into a scenario, where the external API call at times takes far too long to return a response,
casing my code to break, being a serverless function
So I want to set a time limit to the external API call,
Where if I don't get any response from it within 3 secs, I wish the code to gracefully stop the further process
Following is a pseudo-code of what I am trying to do, but couldn't figure out the logic -
let test = async () => {
let externalCallResponse = '';
await setTimeout(function(){
//this call sometimes takes for ever to respond, but I want to limit just 3secs to it
externalCallResponse = await externalCall();
}, 3000);
if(externalCallResponse != ''){
return true;
}
else{
return false;
}
}
test();
Reference -
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SSM.html#getParameters-property
I'm using AWS SSM's getParameters method
You cannot await setTimeout as it doesn't returns a Promise.
You could implement a function that returns a Promise which is fulfilled after 3 seconds.
function timeout(seconds) {
return new Promise((resolve) => {
setTimeout(resolve, seconds * 1000)
});
}
You can await the above function in your code passing the number of seconds you want to wait for
let test = async () => {
let externalCallResponse = '';
setTimeout(async () => {
externalCallResponse = await externalCall();
}, 0);
await timeout(3); // wait for 3 seconds
if(externalCallResponse != '') return true;
else return false;
}
Following code snippet demonstrates the usage of timeout function written above. It mocks a api request that returns a response after 4 seconds.
function timeout(seconds) {
return new Promise(resolve => {
setTimeout(resolve, seconds * 1000);
});
}
function apiRequest() {
return new Promise(resolve => {
setTimeout(() => resolve('Hello World'), 4000);
});
}
let test = async () => {
let externalCallResponse = '';
setTimeout(async () => {
externalCallResponse = await apiRequest();
}, 0);
await timeout(3); // wait for 3 seconds
if (externalCallResponse != '') return true;
else return false;
};
test()
.then(res => console.log(res))
.catch(err => console.log(err.message));
you can use something like this, I created a function that return a promise then I used this promise.
let test = async () => {
return promiseTimeout()
}
const promiseTimeout = () => {
return new Promise(async (resolve, reject) => {
setTimeout(function () {
let externalCallResponse=""
externalCallResponse = await externalCall();
if (externalCallResponse != '') {
return resolve(true);
}
else {
return resolve(false);
}
}, 3000);
})
}
test().then(result=>{
console.log(result);
});
You could do something like this:
const timeout = async (func, millis) => {
return new Promise(async (resolve, reject) => {
setTimeout(() => reject(), millis);
resolve(await func());
});
}
timeout(() => doStuff(), 3000)
.then(() => console.log('worked'))
.catch(() => console.log('timed out'));
Tests:
const timeout = async (func, millis) => {
return new Promise(async (resolve, reject) => {
setTimeout(() => reject(), millis);
resolve(await func());
});
}
const doStuffShort = async () => { // Runs for 1 second
return new Promise((resolve) => setTimeout(() => resolve(), 1000));
}
const doStuffLong = async () => { // Runs for 5 seconds
return new Promise((resolve) => setTimeout(() => resolve(), 5000));
}
timeout(() => doStuffShort(), 3000)
.then(() => console.log('1 worked'))
.catch(() => console.log('1 timed out'));
timeout(() => doStuffLong(), 3000)
.then(() => console.log('2 worked'))
.catch(() => console.log('2 timed out'));

how can I catch the exception when I use promise.all[] or stop the chain call

I'm a little confused about the usage of Promise.
Please see the examples below:
Q1. How can I stop the chain call?
const wait = (duration = 0) => {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
};
const asyncTask1 = () => {
return wait(1000).then(() => {
console.log('task1 takes 1s.');
});
};
const asyncTask2 = () => {
return wait(3000).then(() => {
console.log('task2 takes 3s.');
});
};
Promise.resolve()
.then(() => {
asyncTask1();
})
.then(() => {
asyncTask2();
})
.catch(() => {
console.log('fail.');
});
As the code shows, asyncTask2 won't run until asyncTask1 has completed. But the question is how can I stop the chain call if asyncTask1 fails. Namely asyncTask2 won't run because the asyncTask1 fails
.
Note: no error or exception generated in asyncTask1, I want to use a status (success or failed) to decide the result of asyncTask1.
Q2. How can I know which task generates the exception when I use Promise.all()?
const wait = (duration=0) => {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
};
const asyncTask1 = () => {
return wait(1000).then(() => {
console.log('task1 takes 1s.');
});
};
const asyncTask2 = () => {
return wait(3000).then(() => {
console.log('task2 takes 3s.');
});
};
Promise
.all([asyncTask2(), asyncTask1()])
.then(() => {
console.log('all done.');
})
.catch((e) => {
console.log('error');
});
The code above can run successfully, but if asyncTask1 or asyncTask2 fails, it will go into the catch function. So the question is how can I know whether the exception is from asyncTask1 or asyncTask2?
You can use Array.prototype.map() to call the functions set at an array, throw the index of the element where error occurs within .map() callback at .catch()
const wait = (duration=0) => {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
};
const asyncTask1 = () => {
return wait(1000).then(() => {
console.log('task1 takes 1s.');
});
};
const asyncTask2 = () => {
return wait(3000).then(() => {
throw new Error()
console.log('task2 takes 3s.');
});
};
let arr = [asyncTask2, asyncTask1];
Promise
.all(arr.map((p, index) =>
p().catch(err => {throw new Error(err + " at index " + index)})))
.then(() => {
console.log('all done.');
})
.catch((e) => {
console.log('error', e);
});

Categories

Resources