Like others before me I'm struggling with scope in Javascript. (That and trying to read the darn stuff). I have checked some of the previous threads on this question but I cant seem to get them to apply correctly to my issuue.
In the example below, I want to manipulate the values in the tagsArr array, once the array has been fully populated. I declared the tagsArr variable outside the scope of the function in which it is populated in order to access it globally. But the variable doesn't seem to have the scope I expect - tagsArr.length is 0 at the point where I call output it to console on line 16.
$(function(){
var apiKey = [myapikey];
var tags = '';
var tagsArr = new Array();
$.getJSON('http://api.flickr.com/services/rest/?&method=flickr.people.getPublicPhotos&api_key=' + apiKey + '&user_id=46206266#N05&extras=date_taken,tags&format=json&jsoncallback=?', function(data){
$.each(data.photos.photo, function(i, item) {
var photoID = item.id;
$.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photos.getInfo&api_key=' + apiKey + '&photo_id=' + photoID + '&format=json&jsoncallback=?', function(data){
if (data.photo.tags.tag != '') {
$.each(data.photo.tags.tag, function(j, item) {
tagsArr.push(item.raw);
});
}
});
tags = tagsArr.join('<br />');
console.debug(tagsArr.length);
});
$('#total-dragged').append(data.photos.total);
$('#types-dragged').append(tags);
});
});
Your calls to getJSON are asynchronous. Hence all the calls to the inner getJSON will still be outstanding by the time the console.debug line is reached. Hence the array length is still 0.
You need to run some extra code once the final getJSON call has completed.
$(function(){
var apiKey = [myapikey];
var tags = '';
var tagsArr = new Array();
$.getJSON('http://api.flickr.com/services/rest/?&method=flickr.people.getPublicPhotos&api_key=' + apiKey + '&user_id=46206266#N05&extras=date_taken,tags&format=json&jsoncallback=?', function(data){
var totalExpected = data.photos.total;
var totalFetched = 0;
$.each(data.photos.photo, function(i, item) {
var photoID = item.id;
$.getJSON('http://api.flickr.com/services/rest/?&method=flickr.photos.getInfo&api_key=' + apiKey + '&photo_id=' + photoID + '&format=json&jsoncallback=?', function(data){
if (data.photo.tags.tag != '') {
$.each(data.photo.tags.tag, function(j, item) {
tagsArr.push(item.raw);
totalFetched += 1;
if (totalFetched == totalExpected)
fetchComplete();
});
}
});
function fetchComplete()
{
tags = tagsArr.join('<br />');
console.debug(tagsArr.length);
}
});
$('#total-dragged').append(data.photos.total);
$('#types-dragged').append(tags);
});
});
This works assuming the total number of photos doesn't excede the default 100 per page, other wise you would need to tweak it.
That said I don't think using .each to fire off loads of getJSON requests makes a great deal of sense. I would refactor it so that only one call to getJSON is outstanding at any one time. Have the callback of one issue the next getJSON for the next photo until all have been pulled then do your completed code.
$.getJSON is asynchronous (the a in ajax). That means that by the time you get to console.debug(), getJSON is still getting. You'll need to do some extra work in the JSON callback.
The reason for this is that getJSON is an asynchronous request. after the call to $.getJSON, the javascript engine will move immediately on to the following two lines of code, and will output the length of your array, which is by then, zero-length. Not until after that does the getJSON request receive a response, and add items to the array.
The getJSON function is asynchronous, so when you call the debug function the array is still empty because the requests are not completed. Use the $.ajax function and set async:false and it will work.
$.ajax({
type: "GET",
url: 'http://api.flickr.com/services/rest/?&method=flickr.photos.getInfo&api_key=' + apiKey + '&photo_id=' + photoID + '&format=json&jsoncallback=?',
dataType: "json",
async:false,
success:function(data){
if (data.photo.tags.tag != '') {
$.each(data.photo.tags.tag, function(j, item) {
tagsArr.push(item.raw);
});
}
}
});
This isn't a scope issue - the problem is that getJSON is asynchronous, so it continues executing immediately after sending the request to flickr. By the time the browser executes console.debug the request hasn't returned and you haven't finished handling the response (and therefore haven't pushed any items into the array yet).
To solve this, find all the code that should only be executed when the array is full and move it into your getJSON callback method:
if (data.photo.tags.tag != '') {
$.each(data.photo.tags.tag, function(j, item) {
tagsArr.push(item.raw);
});
tags = tagsArr.join('<br />');
console.debug(tagsArr.length);
$('#total-dragged').append(data.photos.total);
$('#types-dragged').append(tags);
}
You may want to check the answer to this question I posted. There is some good information on scope issues in javascript.
Related
I know there are lot of question regarding this but still I am unable to find a proper answer which makes my code run properly.
I have one function defined to call ajax which I cannot change due to security issue. This is how I call that function
var JsonIQDetails = JSON.stringify(input);//Some input
//pram 1:MethodUrl, 2:JsonObject, 3:ReturnType, 4:SuccessCallBackFunction
InvokeAjaxCall(Url, JsonIQDetails, "json", Success);
I have array of objects (more than 500). Since JSON is getting very long so I am unable to make ajax call. Again due to security issue I can't change config file too. So JSON length cannot be increased.
I am dividing the array into small chunks of 100 and calling the method
for (i = 0, j = mainObject.length; i < j; i += chunk) {
var newSubObject = mainObject.slice(i, i + chunk);
InvokeAjaxCall(Url, newSubObject, "json", Success);
function Success(data) {
if (!data) {
alert("Failed");
break;
}
}
}
Its moving without completing the for loop and executing the next code. So I want first it to complete the for loop (Probably asynchronous)
Thanks in Advance..!!!
Ajax is by default Asynchronous, so you pretty much need to invoke the next part of your ajax call in your success function. Here is a recursive loop that takes care of that.
var ajaxRecursive = function(i, j, c){
if(i < j){
var newSubObject = mainObject.slice(i, i + chunk);
InvokeAjaxCall(Url, newSubObject , "json", function(data){
//do stuff with data
ajaxRecursive(i+=chunk, j,chunk);
});
}
}
ajaxRecursive(0, mainObject.length, chunk);
Supposing that the other variables within ajaxRecursive are defined globally.
Update description:
You can get rid of your "success" function and just create it annonymously.
Is it possible to get the modified timestamp of a file using just JavaScript?
I use a JSON file to fill a page by javascript and I would like to show the timestamp of that JSON file.
You can do it if you're retrieving the file through true ajax (that is, through XMLHttpRequest), provided you configure your server to send the Last-Modified header when sending the data.
The fundamental thing here is that when you use XMLHttpRequest, you can access the response headers. So if the server sends back Last-Modified, you can use it:
var xhr = $.ajax({
url: "data.json",
success: function(response) {
display("Data is " + response.data + ", last modified: " + xhr.getResponseHeader("Last-Modified"));
}
});
Just tried that on Chrome, Firefox, IE8, and IE11. Worked well (even when the data was coming from cache).
You've said below that you need to do this in a loop, but you keep seeing the last value of the variable. That tells me you've done something like this:
// **WRONG**
var list = /*...some list of URLs...*/;
var index;
for (index = 0; index < list.length; ++index) {
var xhr = $.ajax({
url: list[index],
success: function(response) {
display("Data is " + response.data + ", last modified: " + xhr.getResponseHeader("Last-Modified"));
}
});
}
The problem there is that all of the success callbacks have an enduring reference to the xhr variable, and there is only one of them. So all the callbacks see the last value assigned to xhr.
This is the classic closure problem. Here's one solution:
var list = /*...some list of URLs...*/;
list.forEach(function(url) {
var xhr = $.ajax({
url: url,
success: function(response) {
display("Data for " + url + " is " + response.data + ", last modified: " + xhr.getResponseHeader("Last-Modified"));
}
});
});
Since each iteration of the forEach callback gets its own xhr variable, there's no cross-talk. (You'll need to shim forEach on old browsers.)
You said below:
I already thought about a closure problem, thats why I used an array xhr[e] in my loop over e...
But your example doesent help...
and linked to this code in a gist:
//loop over e....
nodename=arr[e];
node_json=path_to_node_json+nodename;
html +='data</td>'
+'</tr>';
xhr[e] = $.ajax({
url: node_json,
success: function(response) {
$('#host_'+nodename).append("last modified: " + xhr[e].getResponseHeader("Last-Modified"));
}
});
That still has the classic error: Your success function closes over the variable e, not the value it had when the success function was created, and so by the time the success function runs, e has the last value assigned to it in the loop.
The forEach example I gave earlier fits this perfectly:
// (I assume `node_json`, `html`, and `path_to_node_json` are all declared
// here, outside the function.)
arr.forEach(function(nodename) {
var xhr; // <=== Local variable in this specific call to the iteration
// function, value isn't changed by subsequent iterations
node_json=path_to_node_json+nodename;
html +='data</td>'
+'</tr>';
xhr = $.ajax({
url: node_json,
success: function(response) {
// Note: You haven't used it here, but just to emphasize: If
// you used `node_json` here, it would have its value as of
// the *end* of the loop, because it's not local to this
// function. But `xhr` is local, and so it isn't changed on
// subsequent iterations.
$('#host_'+nodename).append("last modified: " + xhr.getResponseHeader("Last-Modified"));
}
});
});
I have a JSON file that keeps URLs. I made a $.each() loop to go through the JSON file and then do a SoundCloud function on each iteration of the loop. So in order to get same result of the loop I have to make a callback after each iteration for my SoundCloud function. Here is what I tried:
$.getJSON("http://www.json-generator.com/api/json/get/bLjOHIYsAy?indent=2", function(data){ //Link of the playlist
$.each(data.PlayListArray, function(key, val){ //navigate to array called PlayListArray
var songLink = val.URL; // the value of URL in the array
}, function(){// Callback Function
SC.get('/resolve', { url: songLink }, function(track) {
$("#demo").append("<p id= "+ track.id + ">" + track.title + "</p>");
});
});
});
Howevere this callback does not work and does not show the gathered data from SC.get() function of SoundCloud.
Any idea to make it working? Or How can I have deferred method to make a chain of functions ??
demo: http://jsfiddle.net/Fq2Rw/5/
What you're doing here is passing a 3rd parameter to each which is expecting only two. Unless I'm missing something about your exact goal, the code below should work as expected.
SC.initialize({
client_id: "b8f06bbb8e4e9e201f9e6e46001c3acb",
});
$.getJSON("http://www.json-generator.com/api/json/get/bLjOHIYsAy?indent=2", function(data){ //Link of the playlist
$.each(data.PlayListArray, function(key, val){ //navigate to array called PlayListArray
var songLink = val.URL; // the value of URL in the array
SC.get('/resolve', { url: songLink }, function(track) {
$("#demo").append("<p id= "+ track.id + ">" + track.title + "</p>");
});
});
});
EDIT: I now understand that you want to get the results in the order defined by the data.PlayListArray result set. I think you need to buffer the results and process them only when all answers have been received. The code below is probably not optimal but it may give you some new ideas.
There's is, of course, no way to control in which order the different 'SC.get()' will respond and, consequently, in which order your 'function(track)' callbacks will be called. Waiting for each answer before making the next call -- like your original post is suggesting -- is indeed another possibility, but it will be significantly slower than making all calls in parallel (like the code below does).
SC.initialize({
client_id: "b8f06bbb8e4e9e201f9e6e46001c3acb",
});
$.getJSON("http://www.json-generator.com/api/json/get/bLjOHIYsAy?indent=2", function(data){ //Link of the playlist
var answer = {};
$.each(data.PlayListArray, function(key, val){ //navigate to array called PlayListArray
var songLink = val.URL; // the value of URL in the array
SC.get('/resolve', { url: songLink }, function(track) {
answer[songLink] = track;
if(Object.keys(answer).length == data.PlayListArray.length) {
// we've got all results: let's process them by iterating on data.PlayListArray again
$.each(data.PlayListArray, function(key, val){
var track = answer[val.URL];
$("#demo").append("<p id= "+ track.id + ">" + track.title + "</p>");
});
}
});
});
});
If you want to do async calls in a loop, the rule of thumb is to use a recursive function instead of a for loop.
function loop(i){
if(i >= data.PlayListArray.length){
//DONE
}else{
SC.get('blabla', function(track){
//Loop body
loop(i+1);
});
}
}
loop(0);
As Arnaulid already pointed out, this sequential execution of callbacks is going to exibit high latency so first be sure that this is what you really want to do.
As already pointed out, $.getJSON takes arguments (url, callback), however, you can also chain .then(callback), which is the "promisy" way to do it.
Simplest way to ensure that the displayed list is in the same order as the array from which it is derived, is to append an empty p element, then populate it with data when it arrives. You can rely on closure formed by the $.each function to keep a reliable reference to the appended p.
$.getJSON("http://www.json-generator.com/api/json/get/bLjOHIYsAy?indent=2").then(function(data) { //Link of the playlist
var $demo = $("#demo");
$.each(data.PlayListArray, function(i, item) { //traverse the PlayListArray array
var $p = $("<p/>").appendTo($demo);//appending an empty <p></p> here ensures the observable playList will be in the same order as data.PlayListArray.
SC.get('/resolve', { url: item.URL }, function (track) {
$p.attr('id', track.id).text(track.title);//flesh out the <p></p> appended above. $p remains available due to closure formed by the outer function.
});
});
});
Note also that by assigning $demo ouside the loop avoids the inefficiency of discovering $("#demo") in the DOM on every iteration of the each loop.
Updated fiddle
OK, so I cannot seem to be able to change the global variable of systemPath after it goes through the ajax.It will work inside of ajax, but I need that updated variable outside of ajax. basically I'm trying to create an array of paths from xml and use them to locate other xml files that I can generate a table from.
Does anyone know what's going on here? Does ajax run before the variable is set and that is why I get an array length of 0 after the ajax?
var systemPath = new Array();
var techDigestArr = new Array();
var addToArray = function(thisarray, toPush){
thisarray.push(toPush);
}
$.ajax({
url: fullPath+"technical/systems/systems.xml",
dataType: ($.browser.msie) ? "text" : "xml",
success: function(data){
var xml;
if (typeof data == "string") {
xml = new ActiveXObject("Microsoft.XMLDOM");
xml.async = false;
xml.loadXML(data);
} else {
xml = data;
}
$(xml).find("system").each(function(){
var urlString = fullPath + "technical/system_" + $(this).attr("id") + "/" + $(this).attr("id") + "tech-digest.xml <br />";
//alert(urlString);
$("#td-articles").append(systemPath.length + urlString);
addToArray(systemPath,urlString);
//systemPath.push(urlString);
});
$("#msg-output").append("total - " +systemPath.length);//Returns 48
},//END SUCCSESS
error: function(){
alert("Sorry - ");
history.go(-1);
}
});//END AJAX CALL
$(document).ready(function(){
//$("#msg-output").append("total - " + systemPath.length); Returns 0
});
The ajax is ran asynchronously. Things execute in this order in your code.
stuff before $.ajax()
$.ajax() initiates an ajax call (while waiting for the response it continues to run the rest of the code)
stuff after $.ajax()
success callback
Note that depending on how fast the call is 3 and 4 might occur in reverse order (not the case here)
So when $(document).ready() is executed the ajax call might not have returned yet, so the code in the success callback didn't have a chance to execute. If you are lucky and have a fast connection than maybe the response will come before document ready, but it's unlikely.
Just so you can see that the global variable gets updated you can set a timeout:
$(document).ready(function(){
setTimeout(function(){
$("#msg-output").append("total - " + systemPath.length);
//if the delay set below is more than the time between the ajax request and the server response than this will print the correct value
},2000);
});
I am trying to pull 5 separate JSON feeds and have it looping where every individual has a nested getJSON.
var feedList [feed1,feed2,feed3,feed4,feed5];
for (feed in feedList) {
var index = 0;
$.getJSON(feedList[feed], function(data) {
$.each(data.items, function(i, obj) {
$('li').append(obj.title + '=' + index + '<br>');
});
index++;
}
}
For some reason in firebug it shows that I go through the for in loop then pull the feed successfully then completely bypass the anything inside the .getJSON loop. It doesn't go back into .getJSON loop until all the feeds are pulled. This messes up the order of the items being pulled and also the order is random every time I refresh the page. (e.g. feed2 is listed first then feed4 next)
I've also tried doing a regular for loop instead of a for in loop but it still changes nothing.
Can anyone help me?
Send the next request on success of the previous one by using a recursive function
function getFeed(feed){
$.getJSON(feed, function(data) {
$.each(data.items, function(i, obj) {
$('li').append(obj.title + '=' + index + '<br>');
});
if(feedList.length > index + 1)
getFeed(feedList[++index]);
}
}
// start
getFeed(feedList[0]);
The order would be random because $.getJSON is an asynchronous request for a file. Put simply each request will take some time to complete, and your function (with the each call) will only be called for each request once each request completes respectively.
The problem arises in the fact that you cannot control which requests will return in which order. Since the all are requested at the same time.
You could get the feeds in order by waiting until each request completes before trying the next request:
var feedList [feed1,feed2,feed3,feed4,feed5];
var index = 0;
function getFeed(index) {
$.getJSON(feedList[index], function(data) {
$.each(data.items, function(i, obj) {
$('li').append(obj.title + '=' + index + '<br>');
});
if (index < feedList.length - 1)
getFeed(++index);
});
}
getFeed(0);
Try using a regular loop, combined with self executing anonymous functions. You may need to explicitly disable asynchronicity for ajax, though.
for ( var i = 0, l = feedList.length; i<l; ++i ) {
(function() {
// $.getJSON code
})();
}
Actually the inner function is probably useless. You can disable asynchronicity with
$.ajaxSetup( { "async": false } );