Cleanup setTimeout inside useffect async function - javascript

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

Related

Fetch, then and setTimeout

How can I do setTimeout in then block?
I set setTimeout there, but program just goes through and does not wait for anything.
const controller = new AbortController();
const { signal } = controller.signal;
return fetch(url, { signal })
.then((response) => {
if (response.status === 404) {
controller.abort();
setTimeout(() => controller.abort(), 5000);
}
return response;
})
.then((response) => {
console.log('in');
setTimeout(() => controller.abort(), 5000);
return response.text();
})
.catch(() => {
return Promise.reject(new Error('empty link'));
});
I tried to set settimeout in then block, in catch block, tried to use for it the function which would return promise. The logic seems simple, but somehow I don't understand something about how to connect then block and settimeout functionality.
The setTimeout() call doesn't block. If you want to "await" for x milliseconds, you can do the following:
const timeout = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
const doSomething = async () => {
await timeout(3000);
// after 3 seconds
}

how to clear setTimeOut when component unmounts

i try to get Items data. if request response less than 1 i have to request again. so i write a recursive function with setTimeout. but when i try to change my route function keeps working. window.clearTimeout() or global.clearTimeOut() not worked here when component unmounts. Do i miss something?
useEffect(() => {
getItems(params);
return () => {
window.clearTimeout();
global.clearTimeout();
}
}, []);
const getItems = async(params) => {
try {
const { data = []} = await axios.get('/some-endpoint',params);
dispatch({ type: ITEMS_START });
if (data.length === 0) {
setTimeout(() => {
getItems(params);
}, 5000);
} else {
dispatch({ type: ITEMS_SUCCESS, payload: { data } });
}
} catch (error) {
dispatch({ type: ITEMS_ERROR, payload: error });
}
}
Use a ref to store the timeout ID and then clear that timeout.
const timeoutRef = React.useRef();
useEffect(() => {
getItems(params);
return () => {
window.clearTimeout(timeoutRef.current);
}
}, []);
const getItems = async(params) => {
try {
const { data = []} = await axios.get('/some-endpoint',params);
dispatch({ type: ITEMS_START });
if (data.length === 0) {
timeoutRef.current = setTimeout(() => {
getItems(params);
}, 5000);
} else {
dispatch({ type: ITEMS_SUCCESS, payload: { data } });
}
} catch (error) {
dispatch({ type: ITEMS_ERROR, payload: error });
}
}
Create a reference you can set your timeout too that the unmount can call back to.
let timeout = null;
useEffect(() => {
getItems();
return () => {
if(timeout)
clearTimeOut(timeout)
}
})
const getItems = () => {
timeout = setTimeOut(() => work, 5000);
}
This is the general idea.
Each SetTimeout ( and setInterval ) returns a number which can be used to clear it. ie, var x = setTimeout(() => console.log('timeout'),1000); clearTimeout(x); will do nothing.

polling on a promise to have always fresh data

i'm trying to fetch an endpoint that return a json object to display the result in my app.
this json return a list of online users, so i need to refresh this data every X seconds/minutes.
i've writed this code:
function getJustBlabData(url) {
return new Promise(resolve => {
const getData = fetch(url);
resolve(getData)
})
}
getJustBlabData('https://justblab.com/baxf/widget.php?q=online_usr')
.then((res) => res.json()
.then((data) => {
this.justBlab = data
this.loading = false
console.log(this.justBlab)
})
)
ive tried with setInterval like:
function getJustBlabData(url) {
return new Promise(resolve => {
setInterval(() => {
const getData = fetch(url);
resolve(getData)
}, 1000)
})
}
but i'm doing something wrong, but what?
Firstly, use fetch directly, since your getJustBlabData function is essentially just return fetch(url) wrapped in a new Promise - but since fetch already returns a Promise, there is no need for that code at all
i.e.
function getJustBlabData(url) {
return new Promise(resolve => {
const getData = fetch(url);
resolve(getData)
})
}
getJustBlabData(url).then ....
is equivalent (but in a lot of ways worse for error handling) to
fetch(url).then ....
If you want to do something every X seconds, do the whole thing in the interval
Like this
setInterval(() => {
this.loading = true;
fetch('https://justblab.com/baxf/widget.php?q=online_usr')
.then((res) => res.json())
.then((data) => {
this.justBlab = data;
this.loading = false;
console.log(this.justBlab);
});
}, 1000);
Now, this.justBlab gets updated every second
Note: Also, I've flattened your .then pyramid into a .then chain
to address the situation where requests could take longer than a second
(() => {
const processBlab = () => {
this.loading = true;
fetch('https://justblab.com/baxf/widget.php?q=online_usr')
.then((res) => res.json())
.then((data) => {
this.justBlab = data;
this.loading = false;
console.log(this.justBlab);
setTimeout(() => processBlab(), 1000);
});
processBlab();
})();
Only reason I put that all in a IIFE is to preserve this - as there is no context in the question as to what this is
You can use a promise to create a "wait" function and the restart the process again (get a new set of data) once that completes.
Here I used a 5 second delay between each cycle.
Doing it this way, if the server takes 10 seconds to reply this will do a new request 5 seconds after that response and avoids piling up requests.
let delayMSTime = 5000;
let howMany = 1;
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
function getJustBlabData(url) {
return new Promise(resolve => {
const getData = fetch(url);
resolve(getData);
})
}
function GoGetEm(myDelay) {
getJustBlabData('https://justblab.com/baxf/widget.php?q=online_usr')
.then((res) => res.json())
.then((data) => {
this.justBlab = data;
this.loading = false;
console.log(this.justBlab);
}).then(() => {
console.log("sleeeping for the "+howMany+" time:" + myDelay);
sleep(myDelay).then(() => {
howMany++;
GoGetEm(myDelay);
console.clear();
});
});
}
GoGetEm(delayMSTime);

React/Ionic: Data from database shows undefined when trying to make Axios request

I'm currently facing an issue where the data I receive from my Firebase database is not displaying in the function I need it to. The function I need the data in runs in useIonViewDidEnter/componentDidMount as I need it to begin polling when the page is entered and in an interval every 30 seconds.
I presume because it's run in the useIonViewDidEnter/componentDidMount the database is not retrieving the user's data in time before the function is run. I am wondering if anyone has any other suggestions on either getting the data earlier or taking the weather polling from the useIonViewDidEnter/componentDidMount elsewhere so the data is filled in time?
I get the data via the code below which also uses an interface User:
const [data, setData] = React.useState<User>();
useEffect(() => {
const userRef = db.collection('users').doc(currentUserID);
userRef.get().then((doc) => {setData(toUser(doc));})
}, [currentUserID])
But I need to access the data to request the weather:
useIonViewDidEnter(() => {
weatherInterval = setInterval(() => {
getWeather()
.then(data => setWeather(data.data))
.catch(error => {
setError(error)
return setShowError(true);
})
}, 30000);
}, );
const getWeather = () => {
setLoading(true);
return axios( {
url: `http://localhost:3000/weather`,
method: 'get',
}).then(res => {
setLoading(false);
console.log(res)
return res;
}).catch(error => {
return error
})
}
I figured it out. I replaced the useIonViewDidEnter() with useEffect() but I also had an issue with the intervals doubling up so I added in the following return statements:
return () => clearInterval(tempInterval);
The updated code to get it to work and get the database information is as followed:
useEffect(() => {
const userRef = db.collection('users').doc(currentUserID);
userRef.get().then((doc) => {
setData(toUser(doc));
})
}, [currentUserID])
useEffect(() => {
tempInterval = setInterval(() => {
sendGetRequest()
.then(data => setItems(data.temps))
.catch(error => {
if (error.response.status === 403) {
logout();
return error;
}
setError(error)
setShowError(true);
return error;
});
}, 30000);
return () => clearInterval(tempInterval);
})
useEffect(() => {
weatherInterval = setInterval(() => {
getWeather()
.then(data => setWeather(data.data))
.catch(error => {
setError(error)
return setShowError(true);
})
}, 30000);
return () => clearInterval(weatherInterval);
})

Nodejs - Retry same function on error callback in production

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

Categories

Resources