Add new element in existing object - javascript

I am using node.js.
I have to add new elements in the object before to send a response to client.
user.getMatch(req.user, function(err, match){
for( k=0; k<match.length; k++){
var userId = {
id : match[k].match_id
};
var user = new User(userId);
console.log('k: ' + k);
user.getUserInfo(function(err2, info){
console.log('k here: ' + k);
if(info){
match[k].foo = info[0].foo;
}
});
}
var response = {
data : match
};
res.json(response);
});
I want to add an element "foo" from user.getUserInfo to the object "match" that was returned by user.getMatch. And then send all the data as response to the client.
But it got an error because "k" inside of user.getUserInfo is not equal to the "k" outside.
I do not know why the both "k" are not equal.
And how will I send a response to the client after performing the loop.
Thanks for your help!

Some problems here:
First, k is not defined so the k you're using is actually a global variable which is not what you want. You need to define it as 'var k'.
Second, the callback function you're passing to user.getUserInfo() is (probably) executed at some unknown time in the future. At this point your loop for (k ... has already finished so the the k variable already has a new value since the value that it had when you called user.getUserInfo(). And here's the tricky part: the code inside your callback function will use k's most recent value. It will not use the value that k had when the function was created.
You can solve this by adding a parameter to your callback function and binding k to it using the .bind method:
user.getMatch(req.user, function(err, match){
var k;
for(k=0; k < match.length; k++){
var userId = {
id : match[k].match_id
};
var user = new User(userId);
console.log('k: ' + k);
var callback = function(k, err2, info){
console.log('k here: ' + k);
if(info){
match[k].foo = info[0].foo;
}
}.bind(null, k);
user.getUserInfo(callback);
}
var response = {
data: match
};
res.json(response);
});
Also, you'd be better off by using .forEach for iterating over an array:
user.getMatch(req.user, function(err, match){
match.forEach(function(curr) {
var userId = {
id : curr.match_id
};
var user = new User(userId);
user.getUserInfo(function(err2, info){
if(info){
curr.foo = info[0].foo;
}
}
});
var response = {
data: match
};
res.json(response);
});
Although Array.forEach can give you your current index in the iteration, this is no longer needed. simply use the curr value (which gives you the current element in the iteration).
Finally, I think the code here is likely to send the response before all user.getUserInfo() calls have been executed. To achieve that you need to know when all user.getUserInfo() have been completed. This can be achieved by adding a variable numLeft which is decremented each time we get a user info. when this variable reaches zero we know that all getUserInfo() have completed and it is therefore safe to send the response back.
user.getMatch(req.user, function(err, match) {
var numLeft = match.length;
match.forEach(function(curr) {
var user = new User({
id : curr.match_id
});
user.getUserInfo(function(err2, info){
if(info) {
curr.foo = info[0].foo;
}
--numLeft;
if (numLeft == 0)
res.json({ data: match });
}
});
});

When you say "k inside and outside" do you mean inside and outside ofuser.getUserInfo(function(err2, info){})?
I am not sure of your context however i can think of two things
Since the function "function(err2, info)" is a callback and is executed asynchronously the context/stack in which k is used within getUserInfo is completely different. So try to pass k while calling i.e.
user.getUserInfo(function(err2, info, k){}). This should work
Try to declare k i.e var k in the closure that you want it to be used
Updating for another part of question
"But I got another issue.. it sends a response to client before it adds the "foo" element. So in the response to client, it only sends the object from "match" without the "foo" element."
That is again because ur code inside get user info gets executed asynchronously.
For this you need to keep a global flag or try to send the response from within getUserInfo
i.e.
var mathLen = match.length;
user.getUserInfo(function(err2, info,k,mathLen)
{
console.log('k here: ' + k);
if(info){
match[k].foo = info[0].foo;
}
if(k==mathLen)
{
var response = {
data : match
};
res.json(response);
}
});

Related

Twitch TV JSON API Issue

So,I am trying to use the twitch API:
https://codepen.io/sterg/pen/yJmzrN
If you check my codepen page you'll see that each time I refresh the page the status order changes and I can't figure out why is this happening.
Here is my javascript:
$(document).ready(function(){
var ur="";
var tw=["freecodecamp","nightblue3","imaqtpie","bunnyfufuu","mushisgosu","tsm_dyrus","esl_sc2"];
var j=0;
for(var i=0;i<tw.length;i++){
ur="https://api.twitch.tv/kraken/streams/"+tw[i];
$.getJSON(ur,function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> "+tw[j]+"<p>"+""+"</p></li>");
if(json.stream==null){
$(".stat").append("<li>"+"Offline"+"</li>");
}
else{
$(".stat").append("<li>"+json.stream.game+"</li>");
}
j++;
})
}
});
$.getJSON() works asynchronously. The JSON won't be returned until the results come back. The API can return in different orders than the requests were made, so you have to handle this.
One way to do this is use the promise API, along with $.when() to bundle up all requests as one big promise, which will succeed or fail as one whole block. This also ensures that the response data is returned to your code in the expected order.
Try this:
var channelIds = ['freecodecamp', 'nightblue3', 'imaqtpie', 'bunnyfufuu', 'mushisgosu', 'tsm_dyrus', 'esl_sc2'];
$(function () {
$.when.apply(
$,
$.map(channelIds, function (channelId) {
return $.getJSON(
'https://api.twitch.tv/kraken/streams/' + encodeURIComponent(channelId)
).then(function (res) {
return {
channelId: channelId,
stream: res.stream
}
});
})
).then(function () {
console.log(arguments);
var $playersBody = $('table.players tbody');
$.each(arguments, function (index, data) {
$playersBody.append(
$('<tr>').append([
$('<td>'),
$('<td>').append(
$('<a>')
.text(data.channelId)
.attr('href', 'https://www.twitch.tv/' + encodeURIComponent(data.channelId))
),
$('<td>').text(data.stream ? data.stream.game : 'Offline')
])
)
})
})
});
https://codepen.io/anon/pen/KrOxwo
Here, I'm using $.when.apply() to use $.when with an array, rather than list of parameters. Next, I'm using $.map() to convert the array of channel IDs into an array of promises for each ID. After that, I have a simple helper function with handles the normal response (res), pulls out the relevant stream data, while attaching the channelId for use later on. (Without this, we would have to go back to the original array to get the ID. You can do this, but in my opinion, that isn't the best practice. I'd much prefer to keep the data with the response so that later refactoring is less likely to break something. This is a matter of preference.)
Next, I have a .then() handler which takes all of the data and loops through them. This data is returned as arguments to the function, so I simply use $.each() to iterate over each argument rather than having to name them out.
I made some changes in how I'm handling the HTML as well. You'll note that I'm using $.text() and $.attr() to set the dynamic values. This ensures that your HTML is valid (as you're not really using HTML for the dynamic bit at all). Otherwise, someone might have the username of <script src="somethingEvil.js"></script> and it'd run on your page. This avoids that problem entirely.
It looks like you're appending the "Display Name" in the same order every time you refresh, by using the j counter variable.
However, you're appending the "Status" as each request returns. Since these HTTP requests are asynchronous, the order in which they are appended to the document will vary each time you reload the page.
If you want the statuses to remain in the same order (matching the order of the Display Names), you'll need to store the response data from each API call as they return, and order it yourself before appending it to the body.
At first, I changed the last else condition (the one that prints out the streamed game) as $(".stat").append("<li>"+jtw[j]+": "+json.stream.game+"</li>"); - it was identical in meaning to what you tried to achieve, yet produced the same error.
There's a discrepancy in the list you've created and the data you receive. They are not directly associated.
It is a preferred way to use $(".stat").append("<li>"+json.stream._links.self+": "+json.stream.game+"</li>");, you may even get the name of the user with regex or substr in the worst case.
As long as you don't run separate loops for uploading the columns "DisplayName" and "Status", you might even be able to separate them, in case you do not desire to write them into the same line, as my example does.
Whatever way you're choosing, in the end, the problem is that the "Status" column's order of uploading is not identical to the one you're doing in "Status Name".
This code will not preserve the order, but will preserve which array entry is being processed
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
if (json.stream == null) {
$(".stat").append("<li>" + "Offline" + "</li>");
} else {
$(".stat").append("<li>" + json.stream.game + "</li>");
}
})
}(i));
}
});
This code will preserve the order fully - the layout needs tweaking though
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
var name = $(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
var stat = $(".stat").append("<li></li>")[0].lastElementChild;
console.log(stat);
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
if (json.stream == null) {
$(stat).text("Offline");
} else {
$(stat).text(json.stream.game);
}
}).then(function(e) {
console.log(e);
}, function(e) {
console.error(e);
});
}(i));
}
});

How to check if key is not found in IndexedDB?

On the indexeddb i want to look if there is a key permanent and do some actions. But if not, i want to make some other actions. I can do the actions if the permanent is there, however when it is not I can get the onerror to work. Is the onerror suppose to do this thing? How can I check if there is not value in it?
var hashtype = 'permanent';
var getPermanent = store.get(hashtype);
getPermanent.onsuccess = function() {
var ivrame = getPermanent.result.value;
};
getPermanent.onerror = function() {
console.log('onerror')
};
See the note under https://w3c.github.io/IndexedDB/#dom-idbobjectstore-get - the get method yields success with undefined if there is no matching record.
So you have a few options:
Use get(key) and test the result for undefined. This works unless undefined is a value you expect to store (it's a valid value)
Use count(key) - the result will be 1 if present, 0 if absent. Easy if you're just testing for existence, but doesn't get you the record.
Use openCursor(key) and test to see if the request's result is a cursor (record present as request.result.value) or undefined (no record in range)
For your code:
var hashtype='permanent';
// #1: Use get
var getPermanent = store.get(hashtype);
getPermanent.onsuccess = function() {
if (getPermanent.result === undefined) {
// no record with that key
} else {
var value = getPermanent.result;
}
};
// #2: Use count
var getPermanent = store.count(hashtype);
getPermanent.onsuccess = function() {
if (getPermanent.result === 0) {
// no record with that key
} else {
...
}
};
// #3: Use cursor
var getPermanent = store.openCursor(hashtype);
getPermanent.onsuccess = function() {
var cursor = getPermanent.result;
if (!cursor) {
// no record with that key
} else {
var value = cursor.value;
}
};
The function assigned to request.onsuccess is a callback function that is always called, regardless of whether the value is present in the store. When there is no corresponding object in the store, the result object will be undefined. When there is a corresponding object in the store, the result object will be defined. So you simply need to check if the object is defined from within the onsuccess callback function.
request.onerror is a separate callback from request.onsuccess. onerror gets called when there is some type of failure in indexedDB (e.g. something like you tried to get a value from a store that doesn't exist, or you tried to put a duplicate object into a store that doesn't permit duplicates). request.onerror does not get called when no value is found as a result of calling store.get, because that is not considered an 'error' in the failure sense.
So, what you want to do is something like this:
var hashtype='permanent';
var getPermanent = store.get(hashtype);
getPermanent.onsuccess = function(event) {
//var ivrame=getPermanent.result.value;
var result = getPermanent.result;
if(result) {
console.log('Got a result!', result);
var ivrame = result;
} else {
console.log('Result was undefined! No matching object found');
}
};
getPermanent.onerror = function() {
console.log('Something went wrong trying to perform the get request');
};
Do not try and access request.result.value. There is no such thing in the case of a get request. When using store.get, request.result contains the matching object you want, or is undefined. When using store.openCursor, request.result contains the cursor, which is defined if there is at least one matching object and you have not already iterated past it. To get the matching object at the cursor's current position, you would use cursor.value. Here, cursor.value will always be defined, because cursor would otherwise be undefined, and you would obviously check for that beforehand.
Instead of using getPermanent.result to access data provided by 'get' request it is better to use event.target.result. It also can be compared with undefined to check absence of requested key:
db = this.result;
var tr = db.transaction("data");
var objstore = tr.objectStore("data");
var getres = objstore.get(0);
getres.onsuccess = function(event)
{
if(event.target.result.data === undefined)
console.log("key not found");
}

Variable in for-loop not being reassigned each iteration

I've got a for loop inside a for loop. The first loop should get a username, get their rating, append them to the same list item, then start over with the next username, rating, append, and so on and so forth until it's gone through ever user in the friends list.
//Get usernames
var current = Parse.User.current();
var relation = current.relation("FriendRelations");
relation.query().find({
success: function(results) {
for (var i = 0; i < results.length; i++) {
//This shouldn't increment until the rating has been retrieved in the
// next function.
theuser = results[i].getUsername();
$('ul').prepend('<li id = "frienditems_' + i + '"><div id ="friendname">' +
results[i].getUsername() + '</div></li>');
//Get friend's rating.
//This is a query within a query.
var GameScore = Parse.Object.extend("Rating");
var query = new Parse.Query(GameScore);
query.equalTo("user", results[i].getUsername());
query.find({
success: function(result) {
//The log just outputs the last user's name each time, because I guess the
//other function already looped through completely, so it's forever set to the last one?**
console.log(theuser);
for (var y = 0; y < result.length; y++) {
var object = result[y].get("Rating");
//Logging the rating.
console.log("Rating:" + object);
$('#frienditems_' + y).append('<p class="friendrating">' + object + '</p>');
}
},
error: function(error) {}
});
}
}
});
Here is my console.log:
John
Rating:5
John
Rating:50
John
Rating:43
John
Rating:80
I want it to be this:
George
Rating:5
Smith
Rating:50
Robert
Rating:43
John
Rating:80
Can anyone help? I've looked at other questions, but I can't figure out how to apply them to my situation. I wish I could at least access the first functions results within the second function.
UPDATE: Mihail's answer really helped me out. The console log now shows data being retrieved in the correct order. But it's still not all appending to its respective list item:
Your issue is caused by the asynchronous call to query.find(). You practically order the browser to retrieve your information from the cloud database and tell him what to do with the result if it retrieves anything successfully, while the code continues execution (in your case, the for loop still iterates through the first collection).
By the time the first request ends, for reaches the end and because the second query's success is in the scope of the first, the variable theuser is still instantiated and has the last value available.
To prevent that, you can change the scope of the variable using a function call with your parameter. I've rewritten your code and it looks like this:
var current = Parse.User.current();
var relation = current.relation("FriendRelations");
var $ul = $('ul')
relation.query().find({
success:function(results){
for(var i = 0; i < results.length; i++) {
theuser = results[i].getUsername();
// you can use <<var $friendrating = $("<li id='frienditems_" + i"'/>").html(...... >>
var $friendrating = $(document.createElement("li")).attr("id", "frienditems_" + i).html('<div class="friendname">' + results[i].getUsername() + '</div>')
$ul.prepend($friendrating);
getUserRating(theuser, $friendrating);
}
}
});
function getUserRating(theUser, $node) {
//Get friend's rating.
//This is a query within a query.
var GameScore = Parse.Object.extend("Rating");
var query = new Parse.Query(GameScore);
query.equalTo("user", theUser);
query.find({
success: function(result) {
//The log just outputs the last user's name each time, because I guess the
//other function already looped through completely, so it's forever set to the last one?**
console.log(theUser);
for (var y = 0; y < result.length; y++) {
var object = result[y].get("Rating");
//Logging the rating.
console.log("Rating:" +object);
$node.append('<p class="friendrating">' + object + '</p>');
}
},
error: function(error) {
}
});
}
Please, be advised that i haven't tested it and i'm not 100% sure it will work on the first try.
because the anonymous callback function success: function(result) {} is called after the for loop finishes in this case, query.find() is an asynchronous call, meaning the for loop will continue independently to the next iteration no matter if the query.find() has returned or not.
variable theuser is always the value of theuser in the last for loop because the for loop is able to complete before any of the callback functions are triggered

Dynamically adjust object names based on parameter

I'm running a script on an apache webserver on a linux box. Based on the parameter I want to change the name of variable(or set it)
The idea is that humDev(lines 11 and 14) is named humDev21 for example. Where devId is the number 21 in this example.
My script looks like this:
function getHumDev(devId){
$.ajax({
async: false,
url: "/url" + devId,
success: function(result) {
var array = result["Device_Num_" + devId].states;
function objectFindByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
humDev = array[i].value;
}
}
return humDev;
};
objectFindByKey(array, 'service', 'some');
}
});
};
If Im looking in the wrong direction, please do let me know. Maybe its bad practice what Im trying. The reason I want to have the object a unique name is because this function is called several times by another function, based on the content of an array. But when I have the humDev object named without the number suffix to make it unique, the content of the object is getting mixed up between the different calls.
I may be off base but I am making some assumptions based on what I understand of what you are trying to do.
First, you need to understand how to do file I/O in node.js. So lets start there:
var pathToFile, //set with file path string
fs = require('fs'), //require the file i/o module API
bunchOfHumDevs = {},
fileContents; //we'll cache those here for repeated use
fs.readFile(pathToFile, function(err, result) {
if (err) {
throw new Error(); //or however you want to handle errors
} else {
fileContents = JSON.parse(result); //assumes data stored as JSON
}
});
function getHumDev(devId) {
//first make sure we have fileContents, if not try again in 500ms
if (!fileContents) {
setTimeout(function() {
getHumDev(devId);
}, 500);
} else {
var array = fileContents["Device_Num_" + devId].states,
i = array.length,
//if 'service' and 'some' are variable, make them params of
//getHumDev()
while (i--) {
if (array[i]['service'] === 'some') {
//store uniquely named humDev entry
bunchOfHumDevs['humDev' + devId.toString()] = array[i].value;
break; //exit loop once a match is found
}
}
}
return null;
}
getHumDev(21);
assuming a match is found for the devId 21, bunchOfHumdevs will now have a property 'humDev21' that is the object (value?) in question. Also, the fileContents are now cached in the program so you don't have to reopen it every time you call the function.

Run function after another function completes JavaScript and JQuery

I need a little help. I'm trying to run my second function "likeLinks();" but only after my first function "getLikeURLs();" is finished. This is because my 2nd function relies on the links Array to execute. It seems like they are trying to run at the same time.
Any help would be appreciated.
var links = [];
var url = '/' + window.location.pathname.split('/')[1] + '/' + window.location.pathname.split('/')[2] + '/'
getLikeURLs();
likeLinks();
function getLikeURLs() {
for (i = 1; i < parseInt(document.getElementsByClassName('PageNav')[0].getAttribute('data-last')) + 2; i++) {
var link = $.get(url + 'page-' + i, function(data) {
//gets the like links from current page
$(data).find('a[class="LikeLink item control like"]').each(function() {
links.push($(this).attr('href')); // Puts the links in the Array
});
});
}
}
function likeLinks() {
for (t = 0; t <= links.length; t++) {
var token = document.getElementsByName('_xfToken')[0].getAttribute('value')
$.post(links[t], {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
}, function(data) {});
}
}
The link variables are actually jQuery deferred objects - store them in an array and then you can use $.when() to create a mew deferred object that only resolves when all of the previous $.get() operations have completed:
function getLikeURLs(url) { // NB: parameter, not global
var defs = [], links = []; // NB: links no longer global
for (...) {
var link = $.get(...);
defs.push(link);
}
// wait for previous `$.get` to finish, and when they have create a new
// deferred object that will return the entire array of links
return $.when.apply($, defs).then(function() { return links; });
}
Then, to start the chain of functions:
getLikeURLs(url).then(likeLinks);
Note that likeLinks will now be passed the array of links instead of accessing it from the global state. That function should also be rewritten to allow you to wait for its $.post calls to complete, too:
function likeLinks(links) {
// loop invariant - take it outside the loop
var token = document.getElementsByName('_xfToken')[0].getAttribute('value');
// create array of deferreds, one for each link
var defs = links.map(function(link) {
return $.post(link, {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
});
});
// and another for when they're all done
return $.when.apply($, defs);
}
p.s. don't put that (relatively) expensive parseInt(document.getAttribute(...)) expression within the for statement - it'll cause it to be evaluated every iteration. Calculate it once outside the loop and store it in a variable. There's a few other places where you're repeating calls unnecessarily, e.g. window.location.pathname.split()
EDIT: My answer discusses the issue but see Alnitak answer for a much better solution.
The get in getLikeURLs and the put in likeLinks are both asynchronous. The calls to both of these function return immediately. When data is returned from the called server at some indeterminate time later, the callback functions are then called. The puts could return before the gets which would be a problem in your case. Also note that JavaScript is NOT multi-threaded so the two methods, getLikeURLs and likeLinks will never run at the same time. The callback functions, on the other hand, might be called at anytime later with no guarantee as to the call back order. For example, the 3rd get/put might return before the 1st get/put in your loops.
You could use $.ajax to specify that the gets and puts are synchronous but this is ill advised because the browser will hang if ANY get/put doesn't return in a reasonable amount of time (e.g. server is offline). Plus you don't have the "multi-tasking" benefit of sending out a lot of requests and having the various servers working at the same time. They would do so serially.
The trick is to simply call likeLinks form the callback function in getLikeURL. Your case is a little tricky because of the for loop but this should work:
var links = [];
var url = '/' + window.location.pathname.split('/')[1] + '/' + window.location.pathname.split('/')[2] + '/'
getLikeURLs();
//likeLinks(); // Don't call yet. Wait for gets to all return.
function getLikeURLs() {
var returnCount = 0; // Initialize a callback counter.
var count = parseInt(document.getElementsByClassName('PageNav')[0].getAttribute('data-last')) + 1;
for (i = 0; i < count; i++) {
var link = $.get(url + 'page-' + (i + 1), function(data) {
//gets the like links from current page
$(data).find('a[class="LikeLink item control like"]').each(function() {
links.push($(this).attr('href')); // Puts the links in the Array
});
// If all gets have returned, call likeLinks.
returnCount++;
if (returnCount === count) {
likeLinks();
}
});
}
}
function likeLinks() {
for (t = 0; t <= links.length; t++) {
var token = document.getElementsByName('_xfToken')[0].getAttribute('value')
$.post(links[t], {
_xfToken: token,
_xfNoRedirect: 1,
_xfResponseType: 'json'
}, function(data) {});
}
}

Categories