I am using the Github API to retrieve data about one of my repos and I am running into trouble with callback functions and recursive functions overlapping (as in recursive functions with callbacks attached to them)
Here is the script on jsfiddle as well as below:
(function () {
'use strict';
function makeAJAXCall(hash, cb) {
$.ajaxSetup({
accept: 'application/vnd.github.raw',
dataType: 'jsonp'
});
$.ajax({
url: hash,
success: function (json) {
//console.info(json);
// Time for callback to be executed
if (cb) {
cb(json);
}
},
error: function (error) {
console.error(error);
// an error happened, check it out.
throw error;
}
});
}
function parseBlob(hash) {
return makeAJAXCall(hash, function (returnedJSON) { // no loop as only one entry
console.log(returnedJSON.data);
return returnedJSON.data.content;
});
}
function walkTree(hash) {
var tree = 'https://api.github.com/repos/myusername/SVG-Shapes/git/trees/' + hash;
return makeAJAXCall(tree, function (objectedJSON) {
var objectList = [], i, entry;
for (i = 0; i < objectedJSON.data.tree.length; i += 1) {
entry = objectedJSON.data.tree[i];
//console.debug(entry);
if (entry.type === 'blob') {
if (entry.path.slice(-4) === '.svg') { // we only want the svg images not the ignore file and README etc
//console.info(entry.path)
objectList.push(parseBlob(entry.url));
}
} else if (entry.type === 'tree') {
objectList.push(walkTree(entry.sha));
}
}
if (cb) {
console.log(objectList);
cb(objectList);
}
return objectList;
});
}
$(document).ready(function () {
var returnedObjects = walkTree('master', function (objects) { // master to start at the top and work our way down
console.info(objects);
});
});
}());
The JSON returned is either blog (file) or tree (directory). If it is a tree the walkTree function is called again. I do not understand how the callback will behave here as well as how to get the data it (should) return(s) out of the function and into the final block at the very bottom.
Can someone clarify how I should be doing this?
Ajax calls are usually asynchronous. That means that when you make the ajax call, it just initiates the ajax call and it finishes some time later. Meanwhile, the rest of your code after the initiation of the ajax call keeps running until completion.
Then, sometime later when the ajax call finishes, the success function is called and, in your case, the callback function gets called by the success function. It is important to understand that the success function is called much later after the makeAJAXCall() function has already finished.
Thus, you cannot return the ajax data from the makeAJAXCall() function because it isn't known yet when that function returns.
In fact, the only two places you can use the results of the ajax call are:
In the success handler directly
In some function that the success handler calls which in your case it the callback function.
So, it is doing you no good to return returnedJSON.data.content; from the callback function. That is just returning into some internal part of the ajax infrastructure and doing nothing. That return value will just be dropped on the floor and lost.
Instead, you need to put whatever code wants to use returnedJSON.data.content right there in that callback function (or pass it to another function with a function call).
Ajax is asynchronous. That means you can't do normal sequential programming when using ajax. Instead, you have to do event driven programming where the event in this case is the callback that is called upon a success completion of the ajax call. All work that uses those ajax results needs to commence from that success handler or the callback that is called from it.
Related
I have looked at several examples of callback functions but they don't seem to answer my question.
I would like myFunction2() (see below code) to be executed after the for loop is completed, unfortunately this is being called before for loop is completed (possibly due to API calls in for loop).
Can someone please advise how I can ensure that myFunction2() is called only after the completion of for loop.
function myFunction(){
for(var i =0; i<array1.length; i++){
// contains code with API calls
}
myFunction2();
}
You could substitute $.when() using .apply(), $.map() for for loop
$.when.apply($, $.map(array1, function(request) {
// do api stuff
return $.ajax(request)
}))
// call `myFunction2` when all api stuff completed
.then(myFunction2
// handle error of api call
, function err(jqxhr, textStatus, errorThrown) {
console.log(errorThrown)
})
Assuming you are using $.ajax you can use array of promises for each call and use $.when() to execute when all those promises are resolved.
function myFunction(){
var promises = array1.map(function(arrayElement){
// arrayElement is same as array1[i] in your loop
var data = // get what you need from arrayElement
// return the promise that is returned from $.ajax`
return $.get(url, data).done(function(resp){
// do whatever you do with the response
});
});
$.when.apply(null, promises).done(function(){
// code here gets called whan all api requests have successfully completed
myFunction2();
}).fail(function(err){
// not all requests succeeded
})
}
This happens because the API is called asynchronously and the JavaScript function is finished till the result from API call gets back,
The simplest solution is by callback. Just give that API call a callback function to execute when it returns, but since you didn't give the API call code, there is one more method to do it.
Make the API code synchronous. It's not recommended to make API calls synchronous because it'll block the rest of the code from working if API call takes a lot of time, but it's possible.
For example: jQuery.ajax() has async (default: true) property which if set to false makes ajax request synchronous.
NOTE: async is deprecated so don't use it, I just told you because it's a way to do it. Use the callback functions like success or fail!
like this (assuming API call is ajax):
$.ajax({
method: "POST",
url: "some.php",
data: { name: "John", location: "Boston" },
success: function2;
})
Hope this answers your question, if not then paste your API call code here.
function two called after loop finish it's process. or you can use count to ensure length and count of loop traverse are equal. then and then only call function 2
<html>
<head>
<title>win_load_vs_doc_load</title>
<script type="text/javascript" src="assets/js/jquery-2.1.4.js"></script>
</head>
<script type="text/javascript">
$arr=["a","a1","a2","a3"];
myFunction();
function myFunction()
{
for(var i =0; i<=$arr.length; i++)
{
if(i==$arr.length)
{
myFunction2();
}
else
{
alert(i);
}
}
}
function myFunction2()
{
alert("myFunction2 called");
}
</script>
</html>
I am loading a geoJSON file using ajax so that I can push the coordinates of the geoJSON MultiPoint feature that is in the file into a (global) array for use in another function. I am having a problem with my callback function, I think. In the code below, I intend to load the data file and push the data pertaining to certain whales into the multipointCoords array. This works fine, but multipointCoords array is not available globally while whales is available. This is confusing me.
This code is also using OpenLayers.
var whales = {};
var multipointCoords = [];
$.getJSON('data/BW2205005.geojson', function(data) {
data.features.forEach(function(feature) {
if (!whales.hasOwnProperty(feature.properties.name)) {
whales[feature.properties.name] = {
"type": "FeatureCollection",
"features": []
};
}
whales[feature.properties.name].features.push(feature);
whales["Free Willy"].features.forEach(function(feature) {
multipointCoords.push(feature.geometry.coordinates);
});
console.log(multipointCoords[0][0]); // gives the right coordinate
});
});
console.log(whales); // is defined
console.log(multipointCoords[0][0]); // is undefined
I have studied the following and still can't fix it:
How to load Open layers 3 geojson vector layer with bbox?
How do I return the response from an asynchronous call?
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
http://api.jquery.com/jQuery.getJSON/
Thanks for any help.
While making ajax calls, we are uncertain about when the success callback will fire, hence we user callbacks in such situations. And pass function as an argument to the called function. In callback function we can pass the data retrieved in ajax response. I could see you are using global-variable in your snippet but you are using them synchronously, in that case value of global-variable is not updated(Values used before ajax success callback)
Try this:
function geoJSON(callback) {
var whales = {};
var multipointCoords = [];
$.getJSON('data/BW2205005.geojson', function(data) {
data.features.forEach(function(feature) {
if (!whales.hasOwnProperty(feature.properties.name)) {
whales[feature.properties.name] = {
"type": "FeatureCollection",
"features": []
};
}
whales[feature.properties.name].features.push(feature);
whales["Free Willy"].features.forEach(function(feature) {
multipointCoords.push(feature.geometry.coordinates);
});
if (typeof callback === 'function') {
callback(whales, multipointCoords);
}
});
});
}
geoJSON(function(whales, multipointCoords) {
console.log(whales);
console.log(multipointCoords);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
You array is defined as an empty array, but its first member - not yet.
You are trying to access your array members before your callback fetches the data and process it. To fix it you may use promises.
var whales = {};
var multipointCoords = [];
$.getJSON('data/BW2205005.geojson')
.then( function(data) {
data.features.forEach(function(feature) {
if (!whales.hasOwnProperty(feature.properties.name)) {
whales[feature.properties.name] = {
"type": "FeatureCollection",
"features": []
};
}
whales[feature.properties.name].features.push(feature);
whales["Free Willy"].features.forEach(function(feature) {
multipointCoords.push(feature.geometry.coordinates);
console.log(multipointCoords[0][0]); // gives the right coordinate
});
});
})
.then( function() {
console.log(whales); // is defined
console.log(multipointCoords[0][0]); // is undefined
});
multipointCoords[0][0]) will be always undefined because you're trying to access a value which is not even initialized by the time your console.log() executes.
Even though it looks like the statements in your code are executed in top-bottom fashion, $.getJSON() is an Asynchronous operation, so JS interpreter will fire your AJAX call (via $.getJSON) and continue to execute the remaining statements in your script. The callback that you're passing to $.getJSON() is invoked only when your AJAX call is success and some response is available.
I've attached a screenshot trying to depict the way in which your statements are executed. As shown below, by the time step 5 is executed, multipointCoords is not populated with your expected data as $.getJSON's callback is not invoked yet. Once the AJAX call is done, callback is invoked (Step 6) and multipointCoords is populated with your expected values. Hence the console.log() inside your callback prints some valid value.
If you intend to use multipointCoords in some other function, you can either invoke that other function in the success callback of $.getJSON or use Promises.
$.getJSON('data/BW2205005.geojson', function(data) {
data.features.forEach(function(feature) {
/* do your logic here
populate multipointCoords*/
multipointCoords.push(feature.geometry.coordinates);
});
}).done(function(){ // done is called only for success scenarios
anotherFunction(multipointCoords);
}).fail(function{
// AJAX call failed, multipointCoords is not populated, handle it
});
Hope this helps :)
I have a functions which should run one AFTER the other, such:
function cutTomatoesAlone(Kg){
// slice my stuff
}
function cookTomatoes(Minutes){
// boil my stuff
}
I call them such:
cutTomatoesAlone(15) // 15kg, need 3 hours!
cookTomatoes(10); // need 10 minutes
But the cookTomatoes(10) finish before my cutTomatoesAlone(15).
How to run cutTomatoesAlone(15) first and when finished, then run cookTomatoes(10) ?
Edit: cutTomatoesAlone() load an external JSON. cookTomatoes(10) work on it.
Learn about promises and deferred objects. Every Ajax function in jQuery returns a promise, so you can easily chain your function calls.
For example:
function cutTomatoesAlone(Kg) {
return $.getJSON(...); // return the promise provided by $.getJSON
}
// called as
cutTomatoesAlone(15).then(function() { // attach callback
cookTomatoes(10);
});
In case of an Ajax call, the promise is resolved once the response was successfully retrieved.
You need the method The setTimeout() which will wait the specified number of milliseconds, and then execute the specified function.
function cutTomatoesAlone(Kg){
// slice my stuff
setTimeout(function() {
cookTomatoes(10)
}, delay);
}
If your functions are independent, it should work the way you expect, assuming you're not doing stuff like making http get requests asynchronously.
If you are, what you need to do is call the second function when the first one returns from its request, using JQuery's $.done() function.
Give cutTomatoesAlone a callback.
var cookingTimePerKg = 10;
function cutTomatoesAlone(Kg, Callback) {
// slice my stuff
// when done and a callback is defined do the callback
if(Callback) Callback(Kg*cookingTimePerKg);
}
Then you could do the following:
cutTomatoesAlone(15, cookTomatoes);
The callback could also be fired on the onComplete of the (potential) XHR request.
Some Function object prototype tuning would make it easier to read
Function.prototype.after = function(callback){
this();
if( typeof(callback) == "function")
callback();
}
a = function(){alert(1)};
a.after( function(){alert(2)} )
So with cooking subject:
var cutThem = function(){
cutTomatoesAlone(15) // 15kg, need 3 hours!
}
cutThem.after( function(){
cookTomatoes(10);
});
this is a proposal for general purpose, when ajax loads are on the game it's better to use their "whenDone" option to supply them a callback.
$("#basket").load("url.extension", {kilos: kg},
function(){
cookTomatoes(10);
});
I have two functions one of which includes multiple json call which are post by nature.
I want these to be synchronous. That is, one should run only upon the completion of the previous post (and if all posts are done and successful I want the second function to fire).
The code structure is somewhat like this:
$.getSomeData = function() {
$.postJSON("iwantdata.htm",{data:data},function(data)){
});
$.postJSON("iwantmoredata.htm",{data:data},function(data)){
});
});
$.useSomeData = function() {
});
The useSomeData must work upon subsequent json calls.
Can anyone please help me? Thanks in advance.
So basically you want something like this:
function chainPost(url1, url2, initialInput, func) {
$.post(url1, {data: initialInput})
.done(function (initialOutput) {
$.post(url2, {data: initialOutput})
.done(function (secondOutput) {
func(initialOutput, secondOutput);
});
});
}
chainPost("iwantdata.htm", "iwantmoredata.htm", 0, function (first, second) {
alert(first);
alert(second);
});
You can just nest them, starting the 2nd one in the completion function of the first and so on:
$.getSomeData = function() {
$.postJSON("iwantdata.htm",{data:data},function(data) {
$.postJSON("iwantmoredata.htm",{data:data},function(data)){
// use the data here
});
});
};
When dealing with asychronous functions, you cannot write code such as:
$.getSomeData();
$.useSomeData();
By definition, the first is asynchronous so it will not have completed yet with the second function is called and javascript does not have the ability to stop JS execution until an asynchronous operation is done.
You could pass your use function to the get function and then it would get called when the data was available as an addition to the above example like this:
$.getSomeData = function(fn) {
$.postJSON("iwantdata.htm",{data:data},function(data) {
$.postJSON("iwantmoredata.htm",{data:data},function(data)){
fn(data);
});
});
};
Then, you'd have a getSomeData(useFn) function that would take an argument of the function to call when all the data was ready.
Deferred objects [docs] are perfect for this. Unfortunately, your code example contains syntax errors and it is not clear how the calls are nested. So, I'm not sure if you want to run both Ajax calls after one another or parallel, but either way is possible.
Here are two examples. Have a look at the documentation for more information and play around with it.
Note: .postJSON is not a built in jQuery method, I assume here that you are returning the return value from the $.ajax (or $.post) function.
Parallel Ajax calls:
$.getSomeData = function() {
var a = $.postJSON("iwantdata.htm", {data:data});
var b = $.postJSON("iwantmoredata.htm", {data:data});
// return a new promise object which gets resolved when both calls are
// successful
return $.when(a, b);
};
// when both calls are successful, call `$.useSomeData`
// it will have access to the responses of both Ajax calls
$.getSomeData.done($.useSomeData);
See: $.when
Chained Ajax calls:
... where the response of the first call is the input for the second one. This is only an example, of course you can pass any data you want.
$.getSomeData = function() {
return $.postJSON("iwantdata.htm", {data:data}).pipe(function(response) {
// execute the second Ajax call upon successful completion
// of the first one
return $.postJSON("iwantmoredata.htm", {data:response});
});
};
// if both Ajax calls are successful, call `$.useSomeData`
// it will have access to the response of the second Ajax call
$.getSomeData.done($.useSomeData);
See: deferred.pipe()
If you have a more complex logic, you can also create, resolve or reject your own deferred objects. Have a look at the examples in the documentation.
I have a function which uses getJSON but its not working like I expected.
function balbla(name, param) {
$.getJSON("/blabla.json?name=" + name + "¶m=" + param, function(data) {
return data.bla;
});
}
When I use alert(data.bla) in the getJSON method it works but when I try return data.bla it doesnt. Also when I create a variable and try to write the value of data.bla to it it simply doesnt work!
// I tried this:
function getRouteData(name, param) {
return $.getJSON('/routes_js.json', {route:name, opt: param});
}
function getRoute(name, param) {
getRouteData(name, param).done(function(data) {
return data.route;
});
}
But when I call getRoute("bla", "blub") it still returns undefined.
AJAX is asynchronous. You cannot easily return a value in such a function that depends on the result of the AJAX call. Change your function to accept a callback:
function balbla(name, param, cb) {
$.getJSON('/blabla.json', {name:name, param: param}, function(data) {
cb(data.bla);
});
}
And use it like this:
balbla('foo', 'bar', function(bla) {
// do stuff
});
An even cleaner way would be returning the jqXHR object:
function balbla(name, param) {
return $.getJSON('/blabla.json', {name:name, param: param});
}
When calling it, use the deferred/promise interface of the jqXHR object to attach a success callback:
balbla('foo', 'bar').done(function(data) {
alert(data.bla);
});
Note that using $.ajax() in synchronous mode is not an option you should consider at all. It may hang the browser's UI (or at least the active tab) until the request finished. Besides that, asynchronous callbacks are the way everyone does it.
If you do not like using callback functions, you could use a preprocessor such as tamejs to generate the asynchronous functions automatically.
The function with your return statement:
function(data) {
return data.bla;
}
… is not being called by your code (it is being called deep inside jQuery), so you have no way to put an assignment of the left hand side of the function call.
It is also being called as part of an asynchronous function, so the balbla function will have finished running and returned before it the anonymous one is ever called.
If you want to do something with the response data, do it inside the anonymous callback function.
getJSON is asynchronous, not synchronous. You need to use a callback so your logic needs to be done in two steps. Calling step and the processing step.