Best practice to handle undefined variables dynamicaly in JavaScript/Nodejs - javascript

Ok, maybe is not the best title, but I lacked inspiration, so here goes:
Let's say you have a "global" (not really) variable to store temporary data and sub data as random users interact with your server. Normally on the first interaction with your server, the main variable will be undefined so you need to handle that case.
Now, what puzzled me about this, is what's the best practice performance wise to do this if there are a lot of users and a lot way more interactions with the variable.
Puzzled? Yeah, I know, words are not my strong point so let me show you in code
So you have
var user_data = [];
Then a function that handles user interaction to store data
function writeData(uid, data_name, data)
Now, on first interaction, user_data[uid][data_name] is undefined, and so it's user_data[uid]
I know you can handle this 2 ways:
With if -
if(!user_data[uid]) user_data[uid] = {}
user_data[uid][data_name] = data
With try/catch
try{user_data[uid][data_name] = data}
catch(e) {user_data[uid] = {}; writeData(uid, data_name, data)}
The if will check on every interaction, and like I said there are a lot.
Try catch will trigger once, but it has a cost as a block (afaik)
Which one is better? Or is there a another better way

#Nertan ,
There is a partiality in your proof :P . I have slightly tweeked the ternary way (same as the order of execution in if way). With this you can conclude.
//var present = require('present');
function test(val,ud,fun) {
var k = 10000000;
var t = Date.now();
for(var i=0; i<k;i++)
{
var uid = Math.ceil(Math.random()*1000);
fun(uid,ud,"value");
}
var tf = Date.now()-t;
return tf;
}
function setValue_Opp(uid,ud,value)
{
(!ud[uid] && (ud[uid] = {})) && (ud[uid].value = value);
}
function setValue_Try(uid,ud,value)
{
try{ ud[uid].value = value}
catch(e){ ud[uid] = {}; setValue_Try(uid,ud,value)};
}
function setValue_Cond(uid,ud,value)
{
if(!ud[uid]) ud[uid] = {}
ud[uid].value = value;
}
var k1=0;
var k2=0;
var k3=0;
for(var i=0;i<10;i++){
k1+=test(1,{}, setValue_Cond);
k2+=test(2,{}, setValue_Try);
k3+=test(3,{}, setValue_Opp);
}
console.log(k1,k2,k3)

I feel we can take advantage of ES6 ternaries as below:
let user_data = {}
const writeData = (uid, data_name, data) => {
((user_data[uid] || (user_data[uid] = {})) && (user_data[uid][data_name] = data ))
console.log(user_data)
// perform write action
}
writeData('1',"test","test1");
writeData('2',"test","test2");
writeData('1',"test","test3");

Ok, so I had to rewrite the test because it doesn't work fine in the Snippet
So I made this for node.js:
var present = require('present');
function test(val,ud,fun) {
var k = 10000000;
var t = present();
for(var i=0; i<k;i++)
{
var uid = Math.ceil(Math.random()*1000);
fun(uid,ud,"value");
}
var tf = present()-t;
console.log("END "+val+" at "+tf);
return tf;
}
function setValue_Opp(uid,ud,value)
{
(ud[uid] || (ud[uid] = {})) && (ud[uid].value = value);
}
function setValue_Try(uid,ud,value)
{
try{ ud[uid].value = value}
catch(e){ ud[uid] = {}; setValue_Try(uid,ud,value)};
}
function setValue_Cond(uid,ud,value)
{
if(!ud[uid]) ud[uid] = {}
ud[uid].value = value;
}
var k1=0;
var k2=0;
var k3=0;
for(var i=0;i<10;i++){
k1+=test(1,{}, setValue_Cond);
k2+=test(2,{}, setValue_Try);
k3+=test(3,{}, setValue_Opp);
}
console.log(k1,k2,k3)
And in the end:
3244.328997004777 3695.0267750024796 3437.6855720058084
Which means:
The best is the classical if
The second best is condintional operators method
And the worst is the try-catch
So it seems the classics win
Edited:
With further tests thanks to #CRayen the best method is :
(!ud[uid] && (ud[uid] = {})) && (ud[uid].value = value);

Related

javascript] Object's value update strangely

The value of the object is updated very strangely.
the current overall system structure is as follows.
There is a server that collects the status of each system.
Send the collected data from the server to the web server through websocket
When the web server receives the websocket, the callback function is called.
In the callback function, the object is updated with the received data.
The problem occurs when updating objects.
Here is the code for that part.
var systemDatas = {};
...
fn_callback = function(data){
fn_set_metric(data);
...
};
...
function fn_set_metric(data){
Object.entries(data).forEach(([apps, appArr]) => {
for(let i = 0; i < appArr.length; i++){
var app = {};
if(appArr[i].name === "GW"){
if(systemDatas.hasOwnProperty("GW")){
var gwDatas = systemDatas["GW"];
Object.keys(gwDatas).map(function(key){
try {
var keyIdx = 0;
for(let j = 0; j < (appArr[i].nodes).length ; j++){
if(appArr[i].nodes[j].name === key){
keyIdx = j;
break;
}
}
if(appArr[i].nodes[keyIdx].health === "on"){
gwDatas[key].process.cpuSystem = appArr[i].nodes[keyIdx].metrics[0].measurements[0].value;
gwDatas[key].process.cpuProcess = appArr[i].nodes[keyIdx].metrics[1].measurements[0].value;
gwDatas[key].memory.memUsed = appArr[i].nodes[keyIdx].metrics[2].measurements[0].value;
gwDatas[key].memory.heapUsed = appArr[i].nodes[keyIdx].metrics[4].measurements[0].value;
gwDatas[key].thread.threadDeamon = appArr[i].nodes[keyIdx].metrics[6].measurements[0].value;
gwDatas[key].thread.threadLive = appArr[i].nodes[keyIdx].metrics[7].measurements[0].value;
gwDatas[key].memory.memMax = appArr[i].nodes[keyIdx].metrics[3].measurements[0].value;
gwDatas[key].memory.heapMax = appArr[i].nodes[keyIdx].metrics[5].measurements[0].value;
gwDatas[key].thread.threadPeak = appArr[i].nodes[keyIdx].metrics[8].measurements[0].value;
gwDatas[key].process.uptime = appArr[i].nodes[keyIdx].metrics[9].measurements[0].value;
gwDatas[key].process.cpuCount = appArr[i].nodes[keyIdx].metrics[10].measurements[0].value;
console.log(key);
console.log(systemDatas["GW"][key].process.uptime);
console.log(systemDatas["GW"][key].process);
console.log(systemDatas["GW"][key]);
console.log(systemDatas["GW"]);
}
}
catch(e) {
console.error(e);
}
});
}
...
}
and the result of executing the function.
console.log
As you can see in the area marked in yellow in the result image. depending on the scope of the object, the value is different.
my expectation is
after systemDatas["GW"]["GW_1"] is updated, systemDatas["GW"]["GW_2"] is updated. sequentially.
but it's behaving in an incomprehensible way
except the callback function there is no part to update systemDatas.
Can you explain why it works this way?
Your code complexity (nesting) is to high - It is not helping you solve the problem.
Fixes
Break the function up into 2-3 separate functions const parseMetricsData, parseGWData; // etc..
Look over latest added Array methods, some of the new ones like [].find will make the code easier to read (MDN Array Docs).
Other tips after code example.
Example:
const systemDatas = {};
// ...
const fn_callback = function (data) {
fn_set_metric(data);
// ...
};
// ...
const parseGWData = (app, gwDatas) => {
for (const key of gwDatas.keys()) {
const gwData = gwData || {},
foundNode = !app.nodes ? null : app.nodes.find(n => n.name === key);
if (!foundNode || foundNode.health !== 'on') continue;
gwData.process.cpuSystem = foundNode.metrics[0].measurements[0].value;
gwData.process.cpuProcess = foundNode.metrics[1].measurements[0].value;
gwData.process.uptime = foundNode.metrics[9].measurements[0].value;
gwData.process.cpuCount = foundNode.metrics[10].measurements[0].value;
gwData.memory.memUsed = foundNode.metrics[2].measurements[0].value;
gwData.memory.heapUsed = foundNode.metrics[4].measurements[0].value;
gwData.memory.memMax = foundNode.metrics[3].measurements[0].value;
gwData.memory.heapMax = foundNode.metrics[5].measurements[0].value;
gwData.thread.threadDeamon = foundNode.metrics[6].measurements[0].value;
gwData.thread.threadLive = foundNode.metrics[7].measurements[0].value;
gwData.thread.threadPeak = foundNode.metrics[8].measurements[0].value;
console.log(key);
console.table(systemDatas.GW[key])
}
};
function fn_set_metric(data) {
for (const [apps, appArr] of Object.entries(data)) {
for (const app of appArr) {
if (app.name !== 'GW' ||
!Object.prototype.hasOwnProperty.call(systemDatas, 'GW')) continue;
parseGWData(systemDatas.GW);
}
}
}
Other code tips:
Put long property chains into variables, either via built-ins (app.nodes.find(app => app.name === key)) or directly.
Use built-ins (Array.prototype.find, for of loops etc. (use whatever your platform/platform version supports (see MDN Array, etc., for more).
Use negative if checks (instead of nesting main part of code in if statements you can check the opposite condition to avoid creating deeply nested code).
~~Consider not mutating static structures until loops/manipulations are complete; E.g., perform manipulations on pure, new, objects and then merge results into static structure(s) - will help you pinpoint issues~~ Consider that appArr may have duplicate app entries which may be overriding each others' values.

JavaScript - Issues recovering a map in an object after being saved in localStorage

I've been dealing with this for some time. I've a list of sections in which the user checks some checkboxes and that is sent to the server via AJAX. However, since the user can return to previous sections, I'm using some objects of mine to store some things the user has done (if he/she already finished working in that section, which checkboxes checked, etc). I'm doing this to not overload the database and only send new requests to store information if the user effectively changes a previous checkbox, not if he just starts clicking "Save" randomly. I'm using objects to see the sections of the page, and storing the previous state of the checkboxes in a Map. Here's my "supervisor":
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
var children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children().length;
for (var i = 0; i < children; i++) {
if (i % 2 == 0) {
var checkbox = $("#ContentPlaceHolder1_checkboxes_div_" + id).children()[i];
var idCheck = checkbox.id.split("_")[2];
this.selections.set(idCheck, false);
}
}
console.log("Length " + this.selections.size);
this.change = false;
}
The console.log gives me the expected output, so I assume my Map is created and initialized correctly. Since the session of the user can expire before he finishes his work, or he can close his browser by accident, I'm storing this object using local storage, so I can change the page accordingly to what he has done should anything happen. Here are my functions:
function setObj(id, supervisor) {
localStorage.setItem(id, JSON.stringify(supervisor));
}
function getObj(key) {
var supervisor = JSON.parse(localStorage.getItem(key));
return supervisor;
}
So, I'm trying to add to the record whenever an user clicks in a checkbox. And this is where the problem happens. Here's the function:
function checkboxClicked(idCbx) {
var idSection = $("#ContentPlaceHolder1_hdnActualField").val();
var supervisor = getObj(idSection);
console.log(typeof (supervisor)); //Returns object, everythings fine
console.log(typeof (supervisor.change)); //Returns boolean
supervisor.change = true;
var idCheck = idCbx.split("_")[2]; //I just want a part of the name
console.log(typeof(supervisor.selections)); //Prints object
console.log("Length " + supervisor.selections.size); //Undefined!
supervisor.selections.set(idCheck, true); //Error! Note: The true is just for testing purposes
setObj(idSection, supervisor);
}
What am I doing wrong? Thanks!
Please look at this example, I removed the jquery id discovery for clarity. You'll need to adapt this to meet your needs but it should get you mostly there.
const mapToJSON = (map) => [...map];
const mapFromJSON = (json) => new Map(json);
function Supervisor(id) {
this.id = id;
this.verif = null;
this.selections = new Map();
this.change = false;
this.selections.set('blah', 'hello');
}
Supervisor.from = function (data) {
const id = data.id;
const supervisor = new Supervisor(id);
supervisor.verif = data.verif;
supervisor.selections = new Map(data.selections);
return supervisor;
};
Supervisor.prototype.toJSON = function() {
return {
id: this.id,
verif: this.verif,
selections: mapToJSON(this.selections)
}
}
const expected = new Supervisor(1);
console.log(expected);
const json = JSON.stringify(expected);
const actual = Supervisor.from(JSON.parse(json));
console.log(actual);
If you cant use the spread operation in 'mapToJSON' you could loop and push.
const mapToJSON = (map) => {
const result = [];
for (let entry of map.entries()) {
result.push(entry);
}
return result;
}
Really the only thing id change is have the constructor do less, just accept values, assign with minimal fiddling, and have a factory query the dom and populate the constructor with values. Maybe something like fromDOM() or something. This will make Supervisor more flexible and easier to test.
function Supervisor(options) {
this.id = options.id;
this.verif = null;
this.selections = options.selections || new Map();
this.change = false;
}
Supervisor.fromDOM = function(id) {
const selections = new Map();
const children = $("#ContentPlaceHolder1_checkboxes_div_" + id).children();
for (var i = 0; i < children.length; i++) {
if (i % 2 == 0) {
var checkbox = children[i];
var idCheck = checkbox.id.split("_")[2];
selections.set(idCheck, false);
}
}
return new Supervisor({ id: id, selections: selections });
};
console.log(Supervisor.fromDOM(2));
You can keep going and have another method that tries to parse a Supervisor from localStorageand default to the dom based factory if the localStorage one returns null.

Pass array values as parameter to function and create json data

I have a scenario where I am passing an array of objects to a function in nodejs, but the same is failing with undefined error.
Here is what I have tried :
var object = issues.issues //json data
var outarr=[];
for(var key in object){
outarr.push(object[key].key)
}
console.log(outarr) // array is formed like this : ['a','b','c','d','e']
for(var i =0; i<outarr.length;i++){
jira.findIssue(outarr[i]) //here I am trying to pass the array objects into the loop one by one
.then(function(issue) {
var issue_number = issue.key
var ape = issue.fields.customfield_11442[0].value
var description = issue.fields.summary
var ice = issue.fields.customfield_15890[0].value
var vice = issue.fields.customfield_15891.value
var sor = issue.fields.labels
if (sor.indexOf("testcng") > -1) {
var val = 'yes'
} else {
var val = 'yes'
}
var obj = {};
obj['ape_n'] = ape;
obj['description_n'] = description;
obj['ice_n'] = ice;
obj['vice_n'] = vice;
obj['sor_n'] = val;
var out = {}
var key = item;
out[key] = [];
out[key].push(obj);
console.log(out)
} })
.catch(function(err) {
console.error(err);
});
});
What I am trying to achieve : I want to pass the array values as a parameter which is required by jira.findissue(bassically passing the issue number) one by one and which should again fetch the values and give a combine json output.
How can I pass this array values one by one in this function and also run jira.findissue in loop.
Any help will be great !! :-)
I have taken a look at the code in your question.
To be honest the code you wrote is messy and contains some simple syntax errors.
A good tip is to use a linter to avoid those mistakes.
More info about linters here: https://www.codereadability.com/what-are-javascript-linters/
To output all results in one array you have to define the array outside the scope of the loop.
I cleaned the code a bit up and use some es6 features. I don't know the context of the code but this is what I can make off it:
//map every value the key to outarr
let outarr = issues.issues.map( elm => elm.key);
//Output defined outside the scope of the loop
let output = [];
//looping outarr
outarr.forEach( el => {
jira.findIssue(el).then(issue => {
//creating the issue object
let obj = {
ape_n: issue.fields.customfield_11442[0].value,
description_n: issue.fields.summary,
ice_n: issue.fields.customfield_15890[0].value,
vice_n: issue.fields.customfield_15891.value,
sor_n: issue.fields.labels.indexOf("testcng") > -1 ? "yes" : "yes",
};
//pushing to the output
output[issue.key] = obj;
}).catch(err => {
console.log(err);
});
});
//ouputing the output
console.log(output);
Some more info about es6 features: https://webapplog.com/es6/

Update an Object in indexed db by ignoring a value

I have written the below code
updatePublication(projectName, publicationId, publicationObj, callback) {
let self = this;
this.initDatabase(function (db) {
let tx = self.db.transaction(self.PUBLICATIONS, self.READ_WRITE);
let store = tx.objectStore(self.PUBLICATIONS);
let index = store.index(self.PROJECT_NAME);
let request3 = index.openCursor(IDBKeyRange.only(projectName));
console.log("hrere");
request3.onsuccess = function () {
let cursor = request3.result;
if (cursor) {
let updateObject = cursor.value;
if (updateObject.publicationID == publicationId) {
updateObject.publicationObject = publicationObj;
cursor.update(updateObject);
callback(publicationId);
}
cursor.continue();
} else {
callback(publicationId);
}
};
});
}
But this give error:
I checked the cause of error. It is beacuse , publicationObj which is passed has an object named _requestObjectBuilder which is of the type Subscriber.
used somewhere in the code like this:
_requestObjectBuilder = interval(1000).pipe(tap(() => {}));
Is there any way i can modify my updatePublication code to ignore this value?
Does indexed db support a query for ignoring a value and saving the data?
Note: If i set publicationObj._requestObjectBuilder = undefined, the data gets saved to indexedDB. But this breaks the functionality where _requestObjectBuilder is used.
Fixed the issue by cloning the object and setting it to undefined
let clonedObject = Object.assign({}, publicationObject);
clonedObject._requestObjectBuilder = undefined;
Now i am updating the clonedObject

Clearing JS response

I am making a call to an API. The API returns a list of results. When it does so - the response is fed into an object which I then use to iterate through and display them.
Here is the function which does that:
var getAvailability = () => {
if (chosenData.hotel == "") {
showError("Please select a location before booking.");
$timeout(() => LTBNavService.setTab('location'), 50);
return;
}
searchResponse = {};
console.log(searchResponse);
WebAPI.getHotelAvailability(genSearchObject()).then((data) => {
searchResponse = data;
$timeout(() => $('[data-tab-content] .search-btn').first().focus(), 50);
generateRoomTypeObject(searchResponse);
}, (data) => searchResponse.error = data.data.errors[0].error);
};
The Problem:
The old results are still displayed until the new set of results are available. This causes a flicker and a delay which is a bad user experience.
The solution:(which i need help with)
What is the best possible way of handling this problem? Ideally, I would like to reset/clear the search response. As in, the new results are delivered and the old ones are cleared. Is this possible from within the getAvailability function?
What would be the best way to achieve this?
The Solution:
Thanks to #Daniel Beck for his suggestion to call the generateRoomTypeObject function and feed it an empty object - +1'd his comment.
This triggered an undefined error in my generateRoomTypeObject function where i was running a few length checks(makes sense, because the object was empty - so there was nothing to do length checks on).
I handled the error by handling the undefined exception and setting the searchResponse to an empty object.
var generateRoomTypeObject = (searchResponse) => {
var ratePlans = searchResponse.ratePlans,
errors = searchResponse.error,
roomTypes = [],
ignoreBiggerRooms = false;
rawRoomsObjs = [];
if (angular.isUndefined(errors)) {
// Iterate over the rate plan
if(ratePlans === undefined){
//generateRoomTypeObject -- Handle undefined by creating new object
searchResponse = {}
}else{
for (var i = 0; i < ratePlans.length; i++) {
var ratePlan = ratePlans[i],
rooms = ratePlan.rooms;
// Iterate over the rooms and add rooms to room object. Also keep a list of room types.
for (var j = 0; j < rooms.length; j++) {
//Stuff here
}
}
}
}

Categories

Resources