How to make a callback for my loop function - javascript

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

Related

How to read XML data with $.ajax and return the value within a $.each loop

For one part of our study-project we need to compare an array of Strings with a xml database. So my idea was to divide in 2 parts (because we need the compare-function twice). First I loop through the array with $.each and then I pass the value to another function that makes an ajax request to the xml, compares the value with each data in xml and if something found, it pushes it to an array which should be returned at the end.
In one function the jquery.each() is called:
function doSomething(){
liste = ["test1","test32","test22"];
$.each(liste, function(index, value){
var checkIt = compareDataBase(value);
if(checkIt.length>0) //do Something
});
}
and this is the function that compares the value and returns an array:
function compareDataBase(foo){
var lists = [];
$.ajax({
type:"GET",
url: "data/database/datenbank.xml",
dataType:"xml",
success: function(xml){
$(xml).find('product').each(function(index, element){
var current_product = $(this).text();
if(current_product.indexOf(foo)>-1)lists.push(current_product);
});
},
error: function(){
console.log("Fehler bei Produkte auslesen");
}
});
return lists;
}
But sadly that doesn't work. "checkIt" is always undefined because it doesnt wait for the ajax...
I tried to use the $.when function or give the compareDataBase()-function a callback but somehow that didnt work neither (I think because
I declared it wrong)
So maybe someone knows how to make this right?
Thanks for your help!
br sebi
You should use callbacks (or promises which are a variation on callbacks), the following solution example uses callbacks:
function compareDataBase(foo, callback){
var lists = [];
$.ajax({
type:"GET",
url: "data/database/datenbank.xml",
dataType:"xml",
success: function(xml){
$(xml).find('product').each(function(index, element){
var current_product = $(this).text();
if(current_product.indexOf(foo)>-1)lists.push(current_product);
});
// notify the callback with the result lists here
callback(lists);
},
error: function(){
console.log("Fehler bei Produkte auslesen");
}
});
}
function doSomething(liste, index){
if ( index < liste.length )
{
compareDataBase(liste[index], function(checkIt){
if(checkIt.length>0) //do Something
// process next list item using callbacks
doSomething(liste, index+1);
});
}
}
// start the process
doSomething(["test1","test32","test22"], 0);
Note that the example solution processes each list item only after the previous item has been processed (it kind of synchronises the callbacks, each callback will call the next one). One can remove this feature and process all asynchronously as follows:
// process all async
var liste = ["test1","test32","test22"];
for (var i=0; i<liste.length; i++) doSomething([liste[i]], 0);

Get the modified timestamp of a file with javascript

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"));
}
});
});

Comparing two arrays in Javascript for different values

Javascript gurus, I need your help.
I need to compare two different arrays and check for different values. The values are coming from the same form multi select element. I tried getting a list of current values (cacheTermList) and checking for new value on change (newTermList). The idea is I want to pass an id to an ajax call if a new value was input, and return some data to the screen.
Code:
var cachedTermList = $('select#edit-categories').val();
if (cachedTermList == null) {
var cachedTermList = new Array();
}
$('select#edit-categories').chosen().change(function() {
var newTermList = $('select#edit-categories').val();
if (cachedTermList != null) {
for(var i = 0; i < newTermList.length; i++) {
alert(newTermList[i]);
if (!($.inArray(newTermList[i], cachedTermList))) {
$.ajax({
type: "GET",
url: "/classifieds/js/term/" + newTermList[i],
success: function(data){
//$('div#term-help-text').html(data);
cachedTermList.push(newTermList[i]);
alert(cachedTermList);
}
});
}
}
} else {
}
});
Bear with me, I don't tend to work with Javascript too often. I was trying to get a current list of values by setting cachedTermList on load, then when the select changes, set a newTermList to the new value of the field, then loop it, and check for a value in that list that is not in the cached list.
While I could see things happen, and dump both term lists and see different values, for the life of me I could not get it to push the found value to the cached list so that the next time the element changes, it doesn't keep sending the same value to the ajax call again and again. After the .push() executes, it just adds ',,,' without values. Where am I going wrong?
It is the typical closure - loop problem. All success callbacks reference the same i. At the time the callbacks are executed, the loop already finished and i will have the value newTermList.length + 1, so newTermList[i] will return undefined.
You have to capture the index or the value by introducing a new scope, which can be done by calling a function (JavaScript has no block scope).
Update: Another problem is that $.inArray does not return a boolean value, but the index of the element or -1. So you have to compare the return value against -1.
$('select#edit-categories').chosen().change(function() {
// ...
for(var i = 0; i < newTermList.length; i++) {
if ($.inArray(newTermList[i], cachedTermList) === -1) {
addTerm(newTermList[i], cachedTermList);
}
}
//...
});
function addTerm(term, target) {
$.ajax({
type: "GET",
url: "/classifieds/js/term/" + term,
success: function(data){
//$('div#term-help-text').html(data);
target.push(term);
alert(target);
}
});
}
Also keep in mind that all Ajax calls will basically be executed at the same time. The loop does not wait until one call finished. If you want to execute the calls one at a time, you can use jQuery's Deferred objects.
You are using ajax in a for loop and pushing the newTermList item in its success handler. Since ajax is async by default the loop is not going to wait for the ajax request to get completed. Try to put the pushing code outside the ajax call. But this will not work if the ajax call fails, may be you dont want to add the item into cache when the ajax call fails.
Try something like this
for(var i = 0; i < newTermList.length; i++) {
alert(newTermList[i]);
if (!($.inArray(newTermList[i], cachedTermList))) {
$.ajax({
type: "GET",
context: i,//Here I am setting the value of i into context which can be used in the success handler using this keyword
url: "/classifieds/js/term/" + newTermList[i],
success: function(data){
//$('div#term-help-text').html(data);
cachedTermList.push(newTermList[this]);
alert(cachedTermList);
}
});
}
}

mulitple JSON feeds in an array

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 } );

Javascript/jQuery variables not giving expected values

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.

Categories