GC won't collect function in setTimeout - javascript

I have done several tests and read a lot about Garbage Collection in Javascript applications but there must be something I'm not getting right.
I have a react app that polls data from a JSON file using fetch every 25 seconds. The function I'm calling is mapped to the props using redux in the App.js. I'm calling the function like this on componentDidMount
this.fetchDisplayContent(1000);
This is the function's definition which is also located in the App.js
fetchDisplayContent = (fetchInterval = 500, repeat = true) => {
clearTimeout(this._contentInterval);
this._contentInterval = setTimeout(() => {
if (this._mode === "preview") {
this.props.fetchContent(config.WEB_SERVER + '/display/fetchDisplayContents?mode=prvw&localLastUpdate=0&display=' + this._displayId, 0, this._webkey);
}
else if (this._mode === "web") { //live web version
this.props.fetchContent(config.WEB_SERVER + '/display/fetchDisplayContents?mode=web&localLastUpdate=0&display=' + this._displayId, 0, this._webkey);
}
else if (this._mode === "module") {
this.props.fetchModuleContent(config.WEB_SERVER + '/display/fetchModuleContents?mode=prvw&module=' + this._module + '&project=' + this._project + '&module_id=' + this._module_id, this._webkey);
}
else { //form offline file
if (typeof config.DISPLAY === "number") {
this.props.fetchContent(this._baseURL + '/data/displayContents.json', 0, this._mode);
} else {
this.props.fetchContent(this._baseURL + '/data/display_' + this._displayId + '_contents/displayContents.json', 0, this._mode);
}
}
if (repeat) {
this.fetchDisplayContent();
}
}, fetchInterval);
}
And this is the action:
export function fetchContent(url, lastUpdated, webkey) {
return (dispatch) => {
let now = new Date();
let rev = now.getTime()/1000;
if (url.includes('?')){
url+="&rev="+rev
}
else{
url+="?rev="+rev
}
if (webkey){
url+="&webkey="+webkey
}
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then((response) => response.json())
.then((content) => {
if (Number(content.last_update)>Number(lastUpdated)){
dispatch(contentFetchSuccess(content))
}
else{
return null;
}
}).catch(() => {
dispatch(contentHasErrored(true));
});
};
}
fetchInterval is originally 25000 but I changed it to 500 for testing purposes and reproduce the issue faster.
Here's you can see that references to the handler provided to the setTimeout are not been cleaned out by the GC and the heap keeps growing over time. Eventually the app freezes after a few days and I suspect this is the reason. I confirmed that the number of listeners doesn't increment in the profile as well as the heap's size when the setTimeout is not running.
Performance Profile
What would be the proper way to call this function in a setTimeout without leaking memory?

Related

React Child Component Is Not Rerendering When Props Are Updated

My parent component takes input from a form and the state changes when the value goes out of focus via onBlur.
useEffect(() => {
let duplicate = false;
const findHierarchy = () => {
duplicationSearchParam
.filter(
(object, index) =>
index ===
duplicationSearchParam.findIndex(
(obj) => JSON.stringify(obj.name) === JSON.stringify(object.name)
)
)
.map((element) => {
DuplicateChecker(element.name).then((data) => {
if (data.status > 200) {
element.hierarchy = [];
} else {
element.hierarchy = data;
}
});
if (duplicate) {
} else {
duplicate = element?.hierarchy?.length !== 0;
}
});
return duplicate;
};
let dupe = findHierarchy();
if (dupe) {
setConfirmationProps({
retrievedData: formData,
duplicate: true,
responseHierarchy: [...duplicationSearchParam],
});
} else {
setConfirmationProps({
retrievedData: formData,
duplicate: false,
responseHierarchy: [],
});
}
}, [duplicationSearchParam]);
I have a child component also uses a useeffect hook to check for any state changes of the confirmationProps prop.
the issue is that the event gets triggered onblur, and if the user clicks on the next button. this function gets processes
const next = (data) => {
if (inProgress === true) {
return;
}
inProgress = true;
let countryLabels = [];
formData.addresses?.map((address) => {
fetch(`/api/ref/country/${address?.country}`)
.then((data) => {
countryLabels.push(data.label);
return countryLabels;
})
.then((countries) => {
let clean = MapCleanse(data, countries);
fetch("/api/v1/organization/cleanse", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(clean),
})
.then((data) => {
if (data.status > 200) {
console.log(data.message);
message.error(getErrorCode(data.message.toString()));
} else {
Promise.all([confirmationProps, duplicationSearchParam]).then(
(values) => {
console.log(values);
console.log(data);
setCleansed(data);
**setCurrent(current + 1);**
inProgress = false;
}
);
}
})
.catch((err) => {
console.log(err);
inProgress = false;
});
})
.catch((err) => {
console.log(err);
inProgress = false;
});
});
console.log(confirmationProps);
};
The important part in the above code snippet is the setCurrent(current + 1) as this is what directs our code to render the child component
in the child component, i have a use effect hook that is watching [props.duplicateData.responseHierarchy]
I do output the values of props.duplicateData.responsehierarchy to the console to see if the updated information gets passed to the child component and it does. the values are present.
I have a conditional render statement that looks like this
{cleansedTree?.length > 0 || treeDuplicate ? (...)}
so although the data is present and is processed and massaged in the child component. it still will not re render or display properly. unless the user goes back to the previous screen and proceeds to the next screen again... which forces a re-render of the child component.
I have boiled it down and am assuming that the conditional rendering of the HTML is to blame. Or maybe when the promise resolves and the state gets set for the confirmation props that the data somehow gets lost or the useefect doesn't pick it up.
I have tried the useefect dependency array to contain the props object itself and other properties that arent directly related
UPDATE: this is a code snippet of the processing that gets done in the childs useeffect
useEffect(() => {
console.log(props.duplicate);
console.log(props.duplicateData);
console.log(props.confirmationProps);
let newArray = props.duplicateData.filter((value) => value);
let duplicateCheck = newArray.map((checker) =>
checker?.hierarchy?.find((Qstring) =>
Qstring?.highlightedId?.includes(UUIDToString(props?.rawEdit?.id))
)
);
duplicateCheck = duplicateCheck.filter((value) => value);
console.log(newArray, "new array");
console.log(duplicateCheck, "duplicate check");
if (newArray?.length > 0 && duplicateCheck?.length === 0) {
let list = [];
newArray.map((dupeData) => {
if (dupeData !== []) {
let clean = dupeData.hierarchy?.filter(
(hierarchy) => !hierarchy.queryString
);
let queryParam = dupeData.hierarchy?.filter(
(hierarchy) => hierarchy.queryString
);
setSelectedKeys([queryParam?.[0]?.highlightedId]);
let treeNode = {};
if (clean?.length > 0) {
console.log("clean", clean);
Object.keys(clean).map(function (key) {
treeNode = buildDuplicate(clean[key]);
list.push(treeNode);
return list;
});
setCleansedTree([...list]);
setTreeDuplicate(true);
} else {
setTreeDuplicate(false);
}
}
});
}
}, [props.duplicateData.responseHierarchy]);
This is a decently complex bit of code to noodle through, but you did say that **setCurrent(current + 1);** is quite important. This pattern isn't effectively handling state the way you think it is...
setCurrent(prevCurrent => prevCurrent + 1)
if you did this
(count === 3)
setCount(count + 1) 4
setCount(count + 1) 4
setCount(count + 1) 4
You'd think you'd be manipulating count 3 times, but you wouldn't.
Not saying this is your answer, but this is a quick test to see if anything changes.
The issue with this problem was that the state was getting set before the promise was resolved. to solve this issue I added a promise.all function inside of my map and then proceeded to set the state.
What was confusing me was that in the console it was displaying the data as it should. but in fact, as I learned, the console isn't as reliable as you think. if someone runs into a similar issue make sure to console the object by getting the keys. this will return the true state of the object, and solve a lot of headache

Cannot stop setTimeout function using clearTimeout because value is null for some reason

In my react-native app, I'm trying to stop setTimeout using clearTimeout. I save an instance of the setTimeout in a global variable.
let timeoutId:any = null;
const doOtp = ()=>{
if(canSendOtp) {
setCanSendOtp(false);
timeoutId = setTimeout(() => { // it has here a numeric value
showNotificationMessage("You can request OTP again")
setCanSendOtp(true)
}, SEND_OTP_TIME_CONSTRAINTS)
// rest of doOtp logic
}
else {
showNotificationMessage("Please wait " + (SEND_OTP_TIME_CONSTRAINTS / 1000) + " seconds before trying again")
}
}
Then when I want to stop the setTimeout using clearTimeout, I see that the value of timeoutId is null. I don't understand why it's happening.
const doLogin = () => {
issueToken(LOGIN_GRANT_TYPE, LOGIN_CLIENT_ID, LOGIN_CLIENT_SECRET, phoneNumber, otp)
.then(res => {
console.log('timeoutId !== null' + timeoutId !== null)
if(timeoutId !== null) { // value here is null - why?
clearTimeout(timeoutId)
}
store().dispatch(setTokenValidity(res))
})
.catch(err => {
showNotificationMessage('Error, something went wrong check logs.')
console.log("issueToken error: " + JSON.stringify(err))
});
}
PROBLEM
setCanSendOtp(true) updates your state which initializes your timeout to null again.
SOLUTION
Put your timeout in Ref. Ref values are persistent across re-renders and state-updates.
const timeoutId:any = React.useRef(null);
const doOtp = ()=>{
if(canSendOtp) {
setCanSendOtp(false);
timeoutId.current = setTimeout(() => { // it has here a numeric value
showNotificationMessage("You can request OTP again")
setCanSendOtp(true)
}, SEND_OTP_TIME_CONSTRAINTS)
// rest of doOtp logic
}
else {
showNotificationMessage("Please wait " + (SEND_OTP_TIME_CONSTRAINTS / 1000) + " seconds before trying again")
}
}
const doLogin = () => {
issueToken(LOGIN_GRANT_TYPE, LOGIN_CLIENT_ID, LOGIN_CLIENT_SECRET, phoneNumber, otp)
.then(res => {
if(timeoutId.current !== null) {
clearTimeout(timeoutId.current)
}
store().dispatch(setTokenValidity(res))
})
.catch(err => {
showNotificationMessage('Error, something went wrong check logs.')
console.log("issueToken error: " + JSON.stringify(err))
});

looping the callback function in node js

This is a piece of code which writes data to a ble device and reads data from it. data is written to the device in the form of a buffer. the value in 'mydata' (AAAD0000) is the command to be written in order to read the data.
function named chara3() consists of write and read function which is a callback function in which the command is passed read back.
My requirement is the 'mydata' value which i said earlier, the last two zeros is the memory address. i need to read the data in different memory addresses starting from zero to 59. That is AAAD0000 to AAAD0059. so of course i need to run a loop. If I'm reading the zeroth location, the code is quite fine and i got the output as well but when i tried to make it inside a loop, the code is all a mess. the read part is not executing.
can any one suggest a better way to read data from zeroth memory location to 59th memory location (AAAD0000 to AAAD0059)???
first command writes to it
then reads data
memory location incremented by 1
this should repeat up to 59
var mydata = 'AAAD0000';
function chara3() {
var buff2 = new Buffer(mydata, 'hex');
SensorCharacteristic.write(buff2, false, function(error) { //[0x002d]
console.log('Writing command SUCCESSFUL',mydata);
if (!error) {
SensorCharacteristic.read((error, data) => {
console.log("i just entered");
if (data.toString('hex') != '0000') {
console.log('Temperature History: ', data.toString('hex'));
enter();
}
else {
console.log('contains null value');
} //else
});
}
function enter()
{
mydata = (parseInt(mydata, 16) + 00000001).toString(16);
}
}); //.write
} //chara3
there's no error. But some part of the code is not executing.
You can use the recursion by wrapping your methods into a promise
async function chara3(mydata = 'AAAD0000') {
if (mydata === 'AAAD0059') {
return;
}
var buff2 = new Buffer(mydata, 'hex');
return new Promise((resolve) => {
SensorCharacteristic.write(buff2, false, function (error) { //[0x002d]
console.log('Writing command SUCCESSFUL', mydata);
if (!error) {
SensorCharacteristic.read(async (error, data) => {
console.log("i just entered");
if (data.toString('hex') != '0000') {
console.log('Temperature History: ', data.toString('hex'));
let next = await chara3(enter())
return resolve(next);
}
else {
console.log('contains null value');
return resolve();
} //else
});
}
}); //.write
});
} //chara3
function enter() {
return (parseInt(mydata, 16) + 00000001).toString(16);
}
Also if you can convert your methods SensorCharacteristic.write and SensorCharacteristic.read into promises you can simply map
function chara3(mydata) {
var buff2 = new Buffer(mydata, 'hex');
await SensorCharacteristic.write(buff2, false);
console.log('Writing command SUCCESSFUL', mydata);
let data = await SensorCharacteristic.read();
if (data.toString('hex') != '0000') {
console.log('Temperature History: ', data.toString('hex'));
enter();
} else {
console.log('contains null value');
}
};
let promiseArray = Array(60).fill().map((_, i) => (parseInt('AAAD0000', 16) + i).toString(16)).map(chara3);
Promise.all(promiseArray).then(() => console.log('done'));

How to make sync call in forEach loop Angular 6

I am trying to check my all 4 images is uploaded to server without any error, then redirect to another page so i am trying to perform some sync checking in my code (I have total 4 images in my imgResultAfterCompress array). below is my code:
if(Boolean(this.updateImage(data.AddId))===true)
{
this.router.navigate(['/job-in-hotels-india-abroad']);
}
updateImage(AddId:number):Observable<boolean>
{
this.cnt=0;
this.uploadingMsg='Uploading Images...';
this.imgResultAfterCompress.forEach( (value, key) => {
if(value!=='')
{
this.itemService.updateImage(this.employer.ID,AddId,key,value).subscribe(data=>{
if(data && data.status == 'success') {
this.uploadingMsg=this.uploadingMsg+'<br>Image No - '+(key+1)+' Uploaded.';
this.cnt++;
}
else
this.alertService.error(data.message);
});
}
if(this.cnt==4)
this.uploadingDone= true;
else
this.uploadingDone= false
});
return this.uploadingDone;
}
Every time i am getting cnt value is 0, i want its value = 4 (completely uploaded all images) then redirection will occurred.
The easier way is to wrap your observables into a single one, using zip operator
https://rxjs-dev.firebaseapp.com/api/index/function/zip
Thus once every request is finished successfully your zipped Observable will be fulfilled.
UPDATE:
This is how I think it should look like. I could miss something specific, but the global idea should be clear
redirect() {
this.updateImages(data.AddId).subscribe(
() => this.router.navigate(['/job-in-hotels-india-abroad']),
error => this.alertService.error(error.message)
)
}
updateImages(AddId: number): Observable<boolean[]> {
this.uploadingMsg = 'Uploading Images...';
const requests: Observable<boolean>[] = [];
this.imgResultAfterCompress.forEach((value, key) => {
if (!value) {
return;
}
requests.push(
this.itemService.updateImage(this.employer.ID, AddId, key, value)
.pipe(
tap(() => this.uploadingMsg = this.uploadingMsg + '<br>Image No - ' + (key + 1) + ' Uploaded.'),
switchMap((data) => {
if (data && data.status == 'success') {
return of(true)
} else {
throwError(new Error('Failed to upload image'));
}
})
)
)
});
return zip(...requests);
}
Finally got the desire result by using forkJoin
Service.ts:
public requestDataFromMultipleSources(EmpId: number,AddId:number,myFiles:any): Observable<any[]> {
let response: any[] = [];
myFile.forEach(( value, key ) => {
response.push(this.http.post<any>(this.baseUrl + 'furniture.php', {EmpId: EmpId, AddId:AddId,ImgIndex:key,option: 'updateAdImg', myFile:value}));
});
// Observable.forkJoin (RxJS 5) changes to just forkJoin() in RxJS 6
return forkJoin(response);
}
my.component.ts
let resCnt=0;
this.itemService.requestDataFromMultipleSources(this.employer.ID,AddId,this.imgResultAfterCompress).subscribe(responseList => {
responseList.forEach( value => {
if(value.status=='success')
{
resCnt++;
this.uploadingMsg=this.uploadingMsg+'<br>Image No - '+(value.ImgIndex+1)+' Uploaded.';
}
else
this.uploadingMsg=this.uploadingMsg+'<br>Problem In Uploading Image No - '+(value.ImgIndex+1)+', Please choose another one.';
});
if(resCnt === this.imgResultAfterCompress.length)
{
this.alertService.success('Add Posted Successfully');
this.router.navigate(['/job-in-hotels-india-abroad']);
}
else
this.alertService.error('Problem In Uploading Your Images');
});
You shouldn't try to make sync call within a loop. It is possible using async/await, but it's bad for app performance, and it is a common anti-pattern.
Look into Promise.all(). You could wrap each call into promise and redirect when all promises are resolved.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

Solving inaccesible variable inside jquery .bind() anonymous function using react lifecycle

I need to have access to the jqconsole variable outside of the anonymously bound function. How do i instantiate it outside of the JQuery habitat? If possible, i would just save it globally or to my state. I did not manage to get it to run this way.
componentDidMount = () => {
// Execute after fully loaded
$(window).bind('load', () => {
const jqconsole = $('#console').jqconsole(
'Welcome to the console!\n',
'>'
);
// register some workarounds
jqconsole.RegisterMatching('{', '}', 'brace');
jqconsole.RegisterMatching('(', ')', 'paran');
jqconsole.RegisterMatching('[', ']', 'bracket');
const startPrompt = () => {
// scroll to bottom -- NOT WORKING YET
$('#console').scrollTop($('#console')[0].scrollHeight);
// Start the prompt with history enabled.
jqconsole.Prompt(true, input => {
let transformedString;
// Manage Output
try {
transformedString = window.eval(input);
if (typeof transformedString === 'object') {
this.writeToConsole(jqconsole, 'object', transformedString);
} else {
this.writeToConsole(jqconsole, 'message', transformedString);
}
// Restart the input prompt.
startPrompt();
} catch (error) {
this.writeToConsole(jqconsole, 'error', error);
// Restart the input prompt.
startPrompt();
}
});
};
// Restart the input prompt.
startPrompt();
});
};
To be precise: I have this class method writeToConsole, and want to call it without having to deliver the instance of jqconsole:
// Manage Output
writeToConsole = (jqconsole, type, message) => {
console.log('writing to console # console');
if (type === 'error') {
jqconsole.Write(message + '\n', 'jqconsole-output-error');
} else if (type === 'message') {
jqconsole.Write(message + '\n', 'jqconsole-output');
} else if (type === 'object') {
jqconsole.Write(JSON.stringify(message) + '\n', 'jqconsole-output');
} else {
console.log('error # writeToConsole');
return null;
}
};
Thank you for your time and help.
I solved my own question, by instantiating my jqconsole to this.myConsole in the constructor. This way it is accessible throughout the whole component.

Categories