How to stop recursive setTimeout function in react (clearTimeout not working)? - javascript

Thanks for taking a look at this. I am making an api call with recursion to check on a task status update. I had implemented this function last week, and it was working?? Not sure why it doesn't anymore. The conditional that doesn't work is if the status is a FAILURE, it doesn't clearout the timeout, but it does enter the conditional.... Timeout is declared outside the function in the correct context.
export async function exponentialBackoff (checkStatus, task_id, timeout, max, delay, callback) {
let status = await checkStatus(task_id);
if (status === "SUCCESS") {
callback();
} else {
if (status === "PENDING") {
timeout = setTimeout(function() {
return exponentialBackoff(checkStatus, task_id, timeout, --max, delay * 2, callback);
}, delay);
} else if (status === "FAILURE" || max === 0){
clearTimeout(timeout);
return "FAILURE";
}
}
}

It looks like a callback hell. I would strongly recommend you to avoid name shadowing and overwriting the arguments.
Also - I believe you don't want to keep the previous timeout alive if a next one was called?
let timeoutInstance;
const clear = (tm) => {
if (tm) clearTimeout(tm);
};
export async function exponentialBackoff (checkStatus, task_id, max, delay, callback) {
let status = await checkStatus(task_id);
if (status === "SUCCESS") {
callback();
} else {
if (status === "PENDING") {
clear(timeoutInstance);
timeoutInstance = setTimeout(function() {
return exponentialBackoff(checkStatus, task_id, --max, delay * 2, callback);
}, delay);
} else if (status === "FAILURE" || max === 0){
clear(timeoutInstance);
return "FAILURE";
}
}
}

Related

Why function is executed although await is used?

I have used await keyword in the main function to wait for the completion of async function call to poll() and yet the function call to my_plot is made before the completion of the poll() function.
async function main() {
getParametersData()
await poll()
my_plot()
}
async function getData() {
const response = await fetch(API)
const message = await response.json()
return message
}
async function poll(count = 1) {
console.log(`Polling ${count}`);
try {
const data = await getData();
if (data && Object.keys(data).length !== 0) {
console.log("Poll", data)
return;
} else {
setTimeout(poll, 5000, ++count);
}
}
catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
setTimeout(poll, 5000, 1);
}
}
async function my_plot() {
console.log("my plot")
}
Code output:
Polling 1
my plot
Polling 2
Polling 3
Poll [1,2,3]
Expected:
Polling 1
Polling 2
Polling 3
Poll [1,2,3]
my plot
Don't use setTimeout directly from within an async function. Instead, use a Promise-based wrapper.
It's surprising that modern ECMAScript doesn't come with an in-box Promise-based version of setTimeout, but it's straightforward to implement:
function delay( timeout ) {
if( typeof timeout !== 'number' || timeout < 0 ) throw new Error( "Timeout must be a non-negative integer milliseconds delay value." );
return new Promise( function( resolve ) {
setTimeout( resolve, timeout );
});
}
Then you can rewrite your poll function with a "real" while loop, like so (below).
I think your poll function should return a true/false value to indicate success or failure to the caller, if you ever need to.
Consider using typeof instead of less safe checks like Object.keys(data).length - or at least using a typeof check before using Object.keys.
Though annoyingly typeof null === 'object', so you will always need a !== null check, grumble...
As an alternative, consider having your own type-guard function (yes, I know this isn't TypeScript), that way you get even stronger guarantees that data contains what you need (as JS does not have static type checking).
async function poll( count = 1 ) {
console.log(`Polling ${count}`);
let i = 0;
do {
try {
const data = await getData();
if( isMyData( data ) ) {
return true;
}
}
catch( err ) {
console.error( err );
}
console.log( "Polling again in 5 seconds." );
await delay( 5000 );
i++;
}
while( i < count );
console.log( `Gave up after ${count} attempts.` );
return false;
}
// Type-guard:
function isMyData( data ) {
return (
( typeof data === 'object' )
&&
( data !== null )
&&
( 'this is my object' in data )
&&
( data['there are many like it but this one is mine'] )
&&
( data.myJavaScriptEngineIsMyBestFriend )
&&
data.itIsMyLife
&&
data.withoutMe_javaScriptIsUseless
&&
data.withoutJavaScript_iAmUseLess > 0
);
}
Note that if you intend to catch errors thrown by getData you should use a minimally scoped try instead of having more logic in there, as generally you won't want to catch unrelated errors.
Using the answer from How to make a promise from setTimeout, you can use a traditional loop.
function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}
async function poll() {
for (let count = 1;; count++) {
console.log(`Polling ${count}`);
try {
const data = Math.random(); // simulated data
if (data < 0.2) { // simulated 20% success rate
console.log("Poll", data)
return data;
} else {
console.log("Retrying in 5 seconds");
await later(5000);
}
} catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
count = 1;
await later(5000);
}
}
}
async function main() {
console.log("Start");
await poll();
console.log("Poll done");
}
main();

javascript Convert standard function to a promise

I have a standard function that successfully checks to see if a particular app is installed. But that function takes a few moments to execute and I need to bind it into a promise.then(function (response) { ...} because the app checker takes too long to execute...causing an async issue with the intended response from the app check function. But I can't get it work.
checkSocialApp only returns a true or false:
function checkSocialApp(objApp) {
if (device.platform == "iOS") {
var scheme = objApp.ios;
} else {
var scheme = objApp.and;
}
if (objApp.appName == "Facebook") {
return true ; // FB doesn't need app, can be logged in via browser
} else {
return appAvailability.check(
scheme,
function() { // success callback
console.log(scheme + " is Installed") ;
return true ;
}, function () { // Error callback
console.log(scheme + " is NOT Installed") ;
alert("You do not have the " +objApp.appName+ " app installed.") ;
return false ;
}
);
}
checkSocialApp(appObj).then(function(response){ // errors on 'then'
if (response == true) { // app IS installed
console.log("CheckApp True") ;
appObj.loginApp() ;
} else if (response == false) { // app IS NOT installed
console.log("CheckApp False") ;
appObj.disableApp() ;
}
}) ;
The above errors out on the .then. So I try to bind it to a promise.resolve:
var promise1 = Promise.resolve(checkSocialApp(appObj)) ;
promise1.then(function(response) {
if (response == true) ...
This executes the checkSocialApp function successfully (as I see proper console messages printing from within that function), but I am not getting the response back into the remaining part of the .then for processing.
You have to do something like this return a promise in your function:
function checkSocialApp(objApp) {
return new Promise( function(resolve)={
if (device.platform == "iOS") {
var scheme = objApp.ios;
} else {
var scheme = objApp.and;
}
if (objApp.appName == "Facebook") {
resolve (true) ; // FB doesn't need app, can be logged in via browser
} else {
return appAvailability.check(
scheme,
function() { // success callback
console.log(scheme + " is Installed") ;
resolve (true) ;
}, function () { // Error callback
console.log(scheme + " is NOT Installed") ;
alert("You do not have the " +objApp.appName+ " app installed.") ;
resolve (false) ;
}
);
}
})
}
checkSocialApp(appObj).then(function(response){ // errors on 'then'
if (response == true) { // app IS installed
console.log("CheckApp True") ;
appObj.loginApp() ;
} else if (response == false) { // app IS NOT installed
console.log("CheckApp False") ;
appObj.disableApp() ;
}
}) ;
Does checkSocialApp usually take a callback? You can wrap it in a promise like this:
function checkSocialAppPromise ( appObj ) {
return new Promise( function ( resolve ) {
checkSocialApp( appObj, resolve )
});
}
What you have should work, strictly speaking. If checkSocialApp(appObject) returns true or false, then you should get it. What you show works. If it isn't, then there must be something odd going on.
const someFunc = () => 5;
Promise.resolve(someFunc()).then(result => console.log(result));
However, it probably won't do what you are trying to do. You can't magically make a long-running synchronous function run async.
For example, if I have this function:
function runSlow() {
const start = Date.now();
while (Date.now() - start < 5000) { } // force a loop to wait 5 seconds
return true;
}
runSlow();
console.log('done');
Which takes 5 seconds to complete. I can't just wrap it up and make it asynchrnous suddenly:
function runSlow() {
const start = Date.now();
while (Date.now() - start < 5000) { } // force a loop to wait 5 seconds
return true;
}
Promise.resolve(runSlow())
.then(response => console.log(response));
While technically it is wrapped in a promise, it still takes 5 seconds to run and it still blocks things (try clicking while it's running and you'll see your browser is unresponsive).
If you want it to run properly, you'll have to modify the function itself to stop being synchronous all together. There isn't any way to just wrap it up in it's own thread or anything.

Javascript function runs both routes

Still have problems with this. When I call this function it appears to return twice??? I get 'test2' in the console followed by 'test'. I also put the callback return value into the console and its false then true. The getwidget is only called once from this.nextwidget which is a button.
getwidget = function (callback) {
var id = get_name_value('salesperson');
var password = 'password';
if (id !== "") {
$.get('http://somewhere.com?id=' + id + '&password=' + password,
function (data, status) {
console.log(status);
if (status === 'success') {
catalogue = $.parseJSON(data);
if (catalogue.status === "error") {
alert(catalogue.message);
} else {
console.log('test');
if (callback) callback(true);
}
} else {
alert("Error connecting to API:" + status);
}
});
}
console.log('test2');
if (callback) callback(false);
};
this.nextwidget = function() {
catindex = catindex + 1;
getwidget(function(trigger) {
if (!trigger && catindex > 0) {
catindex = catindex - 1;
}
if (catindex === catlen - 1) {
document.getElementById("nextwidget").disabled = true;
}
if (catindex !== 0) {
document.getElementById("prevwidget").disabled = false;
}
console.log(catindex);
console.log(trigger);
});
};
Javascript is asynchronous.
$.get operation is asynchronous, because it is a call to the server.
It means that JS is not waiting till the end of this operation and just continuing excuting next block of code.
So, according to your code, it shows 'test2' in a console, than executes callback with false parameter. And only when response from server received, it shows "test" in console and run callback with true parameter.
(certainly, if request was successful, according to your snippet)
Here is simple jsfiddle for better understanding how it works.
Simple async example
function asyncExample() {
setTimeout(function() {
console.log('test');
}, 1000);
console.log('test2')
}
asyncExample();
Because operation in timeout is delayed (like asynchronous), JS will not wait and just continue with next lines. So the result will be:
'test2' first and then 'test'.

Javascript Promises recall until acceptable return

ES6
I want to keep calling this method until it returns 0 as the result. Sometimes it takes 5 seconds to return a value, sometimes it takes 10 seconds. Sometimes it doesnt return the value but i want to keep going until it returns 0. It should wait until the answer has come back or timeout in 10 seconds to recall it.
This is how i tried this, but it is not working for some reason.
function method() {
doSomething(from_api).then(function (status) {
if(status != "0") {
method();
} else {
console.log("success");
}
}), function (error) {
method();
});
}
What is the right way to do this?
I'm guessing you want something like an asynchronous-while-loop.
function asynchronous_while(promise_provider, condition) {
return promise_provider().then(function (result) {
if (condition(result) === true) {
return asynchronous_while(promise_provider, condition);
} else {
return result;
}
});
}
asynchronous_while(function () { // promise provider
return getStatus(from_api);
}, function (result) { // condition
return result != "0";
}).then(function (result) {
console.log("success", result);
// result should now be "0";
});
How long it takes to complete depends entirely on when getStatus(from_api) returns 0. That might be never. The same pitfalls of a traditional while apply.
Your code works. You just had an extra closing parenthesis.
Change:
}), function (error) {
to:
}, function (error) {

AngularJS: how to call recursive function with a delay?

I have such function:
$scope.getContactsByScroll = function() {
$scope.pageN = $scope.pageN + 1;
if (!$scope.allDataIsLoaded){
fetchMyDataService.getContactsByScrollService($scope.pageN).then(function(response) {
if (response.length === 0){
$scope.allDataIsLoaded = true;
}
else if (response.length !== 0 && !$scope.allDataIsLoaded){
angular.forEach(response, function(el) {
$scope.contacts.push(el);
});
//$timeout(function() {
$scope.getContactsByScroll();
//}, 2000);
}
}).catch(function() {
$scope.allDataIsLoaded = true;
});
}
};
but it call themselves several times even, if $scope.allDataIsLoaded is false
when i set timeout: all works like a charm. But i don't think that this is a good solution. How can i delay my function without a timeout?
Using timeout in asynchronous functions is not a good idea:
If your request time is longer then timeout, then you'll get unnecessary requests.
If your timeout is bigger then request time, then you'll get unnecessary lags.
You should use promise chain for reqursive requests. Try something like this:
var asyncService = function ($timeout)
{
var someData = 10;
var service =
{
FetchData: function (someArray)
{
someData++;
//timeout here is just for demonstration of async request
return $timeout(function () { return someData }, 1000)
.then(function (result)
{
return service.ProcessData(result, someArray);
});
},
ProcessData: function (data, someArray)
{
console.log(data);
if (data == 15)
{
return someArray;
}
else
{
someArray.push(data)
return service.FetchData(someArray);
}
}
};
return service;
}
Here's a plunker with demonstration

Categories