Why are my JS Objects added to Array as undefined values? [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I have a function that grabs some values from JSON files, and creates an item object.
var searchIndex = [];
function getSearchTerms(param){
var filePath = 'json/' + param + '.json';
$.getJSON(filePath, function( data ) {
var item = {
param: param,
title: data.title,
person: data.fname + ' ' + data.lname
};
// console.log(item);
// searchIndex.push(item);
return item;
});
}
I can see the item objects with correct properties being created when I check the console.
However, when I try to add the objects to searchIndex array, either within the function or within the loop that calls the getSearchTerms function, I get an array with the correct number of rows, but all the values are undefined.
var contentFiles = [ 'a', 'b', 'c'];
for (var i = 0; i < contentFiles.length; i++) {
searchIndex.push( getSearchTerms(contentFiles[i]) );
}
What stupid thing am I doing wrong here? Thank you in advance for your help.

Remember, reading files from the disk takes a little bit of time. Not a lot, but enough to mess with this little bit of code you're trying to write. Now's a good time for you to learn how to use asynchronous code. Here are some slight alterations to specific lines of code that might be able to help.
async function getSearchTerms(param)
await var item
within your loop...
await getSearchTerms(contentFiles[i])
searchIndex.push(object))
I'm no expert, and this is the first SO question I've ever answered. You might need a .next in there or something. If this doesn't work, look further into the concept of async/await functions. In your code, you're pushing objects that haven't gotten their actual value yet, because reading from disk takes a moment. JS travels line by line without waiting, and sometimes you'll have values that need a second to be sorted out.

Related

How can I make a javascript function never return the same value as before? [duplicate]

This question already has answers here:
How to efficiently randomly select array item without repeats?
(14 answers)
Closed 1 year ago.
I am working on a random generator made using javascript for a html page I am working on, and I have been able to make a function that uses math.random to generate random value from an array of values (in my case pokemon names, and i am generating random pokemon that have been added into this array.) This is linked to a button, and every time the button is pressed the function runs and a new pokemon name is generated.
However, I am struggling to make it so that the function generates a completely different name each time, and sometimes i click the button more than once and it just shows the same pokemon, and i feel it is not a good look as it feels broken sometimes. I was wondering if someone can look at my code and help me out.
var pokemonNames = ["charmander", "squirtle", "bulbasaur"];
function generateRandomPoke() {
var randPoke = pokemonNames[Math.floor(Math.random() * (pokemonNames.length))];
return randPoke;
}
$("#randomizebutton").click( function() {
$("#pokemonname").html(generateRandomPoke);
});
Get the index and remove the element from the array:
function generateRandomPoke() {
var index = Math.floor(Math.random() * (pokemonNames.length))
var randPoke = pokemonNames[index];
pokemonNames.splice(index, 1);
return randPoke;
}
As #jabaa said, you could remove the elements of the array, or another alternative is to have a global variable that stores the names already returned. In case the value to be returned is on the global variable, a new name must be selected.
You have to generate the index until its not match the previous one see the following code
var index = 0;
do{
Index = Math.floor(Math.random() * (pokemonNames.length));
} while(previous_index==index)
previous_index = index;
var randPoke = pokemonNames[index];
return randPoke;
}

I can console.log my array but it's values shows up as undefined when appended to a div

I can console.log the array before and after I step through it to build list items. When I run the code I get 10 list items in my html that all read "undefined" instead of being the values I'm pulling out of my chrome history.
Any ideas as to why?
var urlarray = []
var historyResults = function () {
var microsecondsPerWeek = 1000 * 60 * 60 * 24 * 7;
var oneWeekAgo = (new Date).getTime() - microsecondsPerWeek;
chrome.history.search({
'text': '', // Return every history item....
'startTime': oneWeekAgo // that was accessed less than one week ago.
}, function(historyItems) {
for (var i = 0; i < historyItems.length; ++i) {
urlarray.push(historyItems[i].url);
}
})
console.log(urlarray)
}
historyResults()
function addElement () {
var makeUL = function(data) {
var ul = document.createElement("ul");
// create the UL
console.log(urlarray)
for (i = 0; i < 10; i++) {
var a = document.createElement('a');
a.href = data[i];
a.appendChild(document.createTextNode(data[i]));
console.log(a)
var li = document.createElement("li");
li.appendChild(a);
ul.appendChild(li);
// step through the array and create a link out of the value of the array and append them to the list item
// append the list item to the UL
}
return ul;
// return ul full of li
}
console.log(urlarray)
document.getElementById("arraylist").appendChild(makeUL(urlarray));
// go to html to find div and append the full UL with urlarray as the array
}
addElement()
You have two issues going on.
First, you are logging the array, but your browser does not log it immediately. It does so when it has the CPU available. When you log the array, its not yet populated with values. A moment later, when you expand the array in your browser console, the array is now populated because the evaluation of the array is delayed.
You can see this more clearly if you change your logging statement to: console.log(JSON.stringify(urlarray)). This forces the immediate evaluation of the object and turns it into a JSON string, which can then be written to the browser console a moment later.
For more information on the delayed evaluation of logged objects, see this question.
Okay, this brings us to your second issue. Your logging statement is executing before the callback to chrome.history.search does. That's why the array isn't yet populated. You need to use promises to ensure your code executes in the expected sequence. For this you should use a library like jQuery or Q.
I recommend reading about promises. Whichever library you use, your code will follow this basic structure:
Get a 'deferred' object. I'll call it deferred.
In your callback, resolve deferred with the array: deferred.resolve(urlarray)
Where your logging statement is, get the promise from the deferred object. Return that promise from the historyResults method.
Where you call historyResults, instead do:
historyResults.then(function(urls) {
console.log("promise was resolved!", urls);
// do things with your urls here, like add elements
});
Do things that depend on your urls here, inside this callback. If you do, your code will be guaranteed to execute when the urls array is fully populated and ready to go.
This is a big topic, so google "javascript promises" and good luck. I hope this helps to get you started in the right direction.
If you don't want to use promises:
If you don't want to use promises, you will need to do everything inside the callback to chrome.history.search. That's the only way to guarantee the array is populated.
Asynchronous code is fun.

Run the same function on each item in a list / array

Goal
I have a working function (JSFiddle). On numerous occasions throughout a script the function runs sequentially. In these instances, there is a lot of repetitious code that I would like to consolidate.
Ideally changing code like this:
functionName("First_item") +
functionName("Second_item") +
functionName("Third_item") +
To something like this:
functionName("First_item", "Second_item", "Third_item");
The function will run for each item in the list so the result is the same but the code more elegant and maintainable.
Notes:
I’m not looking to use any libraries (e.g. jQuery) to accomplish the goal.
Solution
Amit Joki’s answer kindly noted I could use arguments. When I implemented the code, the modified function (JSFiddle) only returned the output string for the first argument / item.
Vanice’s answer pointed out the eventual solution.
Make one string from the output of all arguments / items by concatenating (joining) the output strings within the for loop (with the use of +=).
Return the concatenated output by placing the return outside of the for loop.
Example
Working solution (JSFiddle).
Thanks
Thank you very much to everyone for their time and help on this. I really appreciate it!
Leveraging Javascript's Prototype OOP: You can add an each function to Array's themselves, so every array in your code that will automatically have an inhereted each function.
Array.prototype.each = function(callback){
for (var i = 0; i < this.length; i++){
callback(this[i]);
}
}
Usage:
myArray.each(myCoolFunction)
['something','somethingelse',somethingother'].each(myCoolFunction)
myArray.each( function (item) {
// if your item has a method
item.Something();
// if you'd like to call a function on the item:
doSomething(item);
});
caveats:
Because javascript is an asynchronous language that is interpreted differently across various browsers and inherently handles primitive objects and complex objects differently, it is highly recommended usage of underscore or lodash. You can also make your own, but you'll need ensure the objects passed through will be handled by the functions properly. This may include workarounds or creating special callback functions for different object types that are passed through your each function.
For more information: Is JavaScript a pass-by-reference or pass-by-value language?
Libraries you should seriously consider:
lodash:
https://lodash.com/docs#forEach
_([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
// → logs each number and returns '1,2,3'
_.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
// → logs each number and returns the object (property order is not guaranteed across environments)
underscore:
http://underscorejs.org/#each
_.each([1, 2, 3], alert);
=> alerts each number in turn...
You don't need an array. Just use arguments
function functionName(){
for(var i = 0; i < arguments.length; i++){
// do something with arguments[i];
}
}
and then you can do
functionName("shot_type","shot_height","shot_angle","framed","scene_depth");
P.S #codebox's solution works if supporting legacy IE version isn't a problem. Don't know why he deleted it...so putting it here so it helps. His answer using forEach
["shot_type","shot_height","shot_angle","framed","scene_depth"].forEach(FunctionName);
EDIT: Looking at your Fiddle, you have a return inside the for loop - therefore the function will return after the first iteration. Put the return after the for and concatenate the output to one string.
var output = "";
for(...){
output += description_of_object + ": " + randomly_selected_item_from_object + ".\n";
}
// return it
return output;
With Javascript only:
var actions = ["shot_type","shot_height","shot_angle","framed","scene_depth"];
for (var i = 0; i < actions.length; i++){
FunctionName(actions[i]);
}
With JQuery:
$.each(["shot_type","shot_height","shot_angle","framed","scene_depth"], function(index,value){
FunctionName(value);
});
I haven't tested it but it should work.
To avoide redundancy in code use an array with the values, that you want to pass through the function and call the function in an loop.
var vals=["shot_type","shot_height","shot_angle","framed","scene_depth"];
for(var i=0; i<vals.length; i++)
{
FunctionName(vals[i]);
}
If you want to expand the function (adding another parameter) you can just expand the for-loop and the array structure.
Alternatively you could fill an object with the values and handle this logic in an object. But this would just do a difference on calling the function.

node.js String Constant Within Scheduled Execution [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I'm working on a project using node.js. I'm pulling in a chunk of JSON data and I need to schedule jobs to write data out a serial port based on the data received. I'm using node-schedule for this task. There are several jobs that need to be scheduled and the command for each one is different. What I'm finding is that the arguments passed to the function within the scheduleJob call are interpreted at execution time rather than when scheduled within the for loop. Any ideas on how I can get the string argument to be constant?
Code Example:
var schedule = require('node-schedule');
var schedules[];
…
var startrule = new schedule.RecurrenceRule();
var start_hour = thejson.start_hour;
var start_minute = thejson.start_minute;
for (var k = 0; k < thejson.zones.length; k++)
{
//start
startrule.hour = start_hour;
startrule.minute = start_minute;
var start = schedule.scheduleJob(startrule, function(){
writeSerial('//S' + thejson.zones[k].number + ',1');
});
schedules.push(start); //upon next json update, old schedules in array are cancelled.
}
When it executes, thejson.zones[k].number fails because k is unknown. How can I get that string argument to writeSerial() to be static/constant? I've tried sticking it in an array and passing an index to writeSerial within the schedule, but that index variable is also interpreted at schedule execution.
You have to wrap the establishment of the scheduled job in a function to provide a per-job unique lexical scope:
var start = schedule.scheduleJob(startrule, function(number){
return function() {
writeSerial('//S' + number + ',1');
};
}(thejson.zones[k].number));
The parameter "number" for that anonymous and immediately-invoked function ensures that the scheduled function it returns will be working with a safe copy of that part of your data structure.
Without that extra layer, each one of those jobs you scheduled shared the same variable "k". The loop in which "k" is used ends when "k" is equal to the length of the list, and so each job (when it is eventually invoked as scheduled) sees "k" pointing to a spot beyond the end of the array.
My preferred solution would be to create a partial function:
var start = schedule.scheduleJob(startrule, function(number) {
writeSerial('//S' + number + ',1');
}.bind(null, thejson.zones[k].number);
This creates an anonymous function which has its first argument already bound to it.

Return variable within one function to use in another [duplicate]

This question already has answers here:
unable to cope with the asynchronous nature of navigator.geolocation
(5 answers)
Closed 8 years ago.
I'm trying to make it so I can write a function which defines a variable, and then call that function within another function to give me the variable.
Here's what I've done:
function getLat(){
navigator.geolocation.getCurrentPosition(function(lat){
var latCoord = lat.coords.latitude;
return latCoord;
});
}
function getLong(){
navigator.geolocation.getCurrentPosition(function(lon){
var longCoord = lon.coords.latitude;
return longCoord;
});
}
function getImage(){
var the_lat = getLat(),
the_lon = getLong();
console.log(the_lat + ' ' + the_lon);
}
getImage();
As you can see I have the functions getLat(), and getLong() to grab the latitude, as well as the longitude of the person. Then, in getImage() I want to be able to call getLat() in a variable to assign latCoord to that variable. I was hoping return latCoord; would do this, but I was mistaken.
Is this possible, or should I be going about it a different way? If so, may I see an example of how I should be doing this?
Here's a jsbin http://jsbin.com/ulelix/edit
Let me know if I need to clarify anything, or if you need me to add any information and I will do so as appropriate. I've searched around, but can't seem to get the answer I'm looking for. Any help is appreciated, Thanks!
The navigator.geolocation.getCurrentPosition method is asynchronous. Your functions have returned undefined before the callbacks are executed.
You need to move any code that relies on the response from that method into the callback.
I would suggest combining your two functions into one, since both latitude and longitude are available in the callback:
function getLatLng(){
navigator.geolocation.getCurrentPosition(function(pos) {
var lat = pos.coords.latitude,
lng = post.coords.longitude;
//Do stuff relying on lat/lng here...
});
}

Categories

Resources