I have an application that uses several instances of getJSON, and I'm getting into lots of trouble. Pointy once suggested reworking the main routine to include asynchronous processing, and I'm agreeing (now that I understand something of this).
Before attempting to rework this, it was structured like this:
Fill some arrays;
Call processArray to create a set of strings for each;
Stick the strings into the DIVs.
In the processArray routine, I call $.getJSON--a couple times, and you folks have pointed out that I'm getting into trouble with expecting values I have no right to expect. The overall routine processes an array into a complex string, but some arrays have to be sorted (unconventionally) first. My original structure began by asking: is this an array to be sorted? If so, I did such and such, involving getJSON, then returned to the main routine. What I had done to the array did not make it over the main routine, which continued to see the original array contents.
So, that processArray was configured like so:
function processArray(arWorking, boolToSort...) {
if(boolToSort) {do special stuff}
//continue on with processing
return complexString;
}
I figured that I would try to guarantee the inclusion of the sorted array in the main routine by replacing the 'arWorking' argument with a function that did the sorting if processArray was called with boolToSort = true. In my thinking, the rest of the main routine would go on with one of two forms of array: the original as passed or the sorted array. To this end, I made the sorting routine a separate routine: SortArray(arrayToUse).
I came up with this:
function processArray( function(arWorking) {if(boolToSort) SortArray(arWorking); else return arWorking;}, boolToSort, ...) {
//main routine
return complexString;
}
Both FireFox and IE9 object. FF breaks to jQuery, while IE9 wants an identifier in the calling arguments.
What looks to be wrong? Can I use boolToSort in my "argument function?"
The first part of you understanding this is this:
$.getJSON() does it's work asynchronously. That means that when you call it, all it does is start the operation. The code following that function continues to execute while the $.getJSON() call works in the background. Some time later, the JSON results will be available and the success handler will get called.
ONLY in the success handler can you use those results.
As such, you cannot write normal procedural code that does this:
function processArray() {
$.getJSON(url, function(data) {
// only in here can you process the data returns from the getJSON call
})
// you cannot use the JSON data here as it is not yet available
// you cannot return any of the JSON data from the processArray function
}
Instead, you must write code that uses the success handler. Here's one way of doing that:
function processArrays(urlToProcess1, urlToProcess2, callbackWhenDone) {
$.getJSON(urlToProcess1, function(data) {
// only in here can you process the data returns from the getJSON call
// do whatever you want to do with the JSON data here
// when you are done process it, you can then make your next getJSON call
$.getJSON(urlToProcess2, function(data) {
// do whatever you want to do with the JSON data here
// when done, you can then call your callback function to continue on with other work
callbackWhenDone();
});
});
}
Another thing you cannot do is this:
function processArray() {
var result;
$.getJSON(url, function(data) {
// only in here can you process the data returns from the getJSON call
result = data;
})
return(result);
}
var data = processArray();
// code that uses data
You cannot do this because the result data is not available when processArray() returns. That means not only can you not use it inside of processArray (but outside the success handler), but you cannot return it from processArray() and you cannot use it in code written after processArray(). You can only use that data from within the success handler or in code called from the success handler.
If you had a whole bunch of URLs to process and you used the same code on each one, you could pass an array of URLs and loop through them, starting the next getJSON call only when the success handler of the first was called.
If you had a whole bunch of URLs each with different code, you could pass an array of URLs and an array of callback functions (one for each URL).
FYI, I see no issue with passing boolToSort. It sounds to me like the issue is with how you handle asynchronous ajax calls.
For completeness, it is possible use synchronous ajax calls, but that is NOT recommended because it's a bad user experience. It locks up the browser for the duration of the networking operations which is generally not a good thing. It's much better to use the normal asynchronous ajax calls and structure your code to work properly with them.
Related
I have run into quite an annoying issue with my Javascript code due to its synchronous nature, in short the problem is I need to dump a load of data into an array.
This array is then written directly into a JSON file.
function main(callback) {
myarrary = []
//run some SQL query via stored procedure
for (result in query) {
myarray.push({
//my data is entered here
})
}
let data = JSON.stringify({myarray: somedata}, null, 4)
fs.writeFileSync('testingjson.json', data)
callback();
}
main(another_function)
My issue is that the data dump and callback are executed immediately, as such no data is transferred to the file and 'another_function` relies on data being there so that doesn't work either.
Therefore I need the dump to wait for the loop to finish appending myarray
I have also tried entering the dump and callback into my for loop however they execute on the first iteration.
Any help is appreciated!
Functionally your current code looks fine, except that you should not call the function as callback unless it is an asynchronous function.
Callbacks are usually associated with asynchronous API's. It is useful if you want main(...) to return immediately so that you can just continue with something else. Since that is the default behavior, the API is
fs.writeFile(filename, data[, options], callback)
So to convert your program to async, you need:
fs = require("fs");
function main(callback) {
// Oops. Why is query executed synchronously?
query = sqlSync() ;
fs.writeFile('delete.json', "Data", callback);
}
main(() => console.log("Done"))
You can also convert SQL calls to async if your API's support that (since it is an I/O operation).
Once you are comfortable with normal callbacks, you can try using promise or async-await as mentioned here.
if you have two methods who are returning callbacks you can use Promise.all([]). Here in Promise.all() you can write your database calls or other functions which are resolving/rejecting anything. From there you can get them in res object.
So, I wrote the following function:
function getData() {
var data;
$(function () {
$.getJSON('https://ipinfo.io', function (ipinfo) {
data = ipinfo;
console.log(data);
})
})
console.log(data);
}
The problem with the above is the 2nd console.log doesn't retain the info from the assignment inside the jQuery and logs an undefined object. I'm not exactly sure what is wrong, but I believe it to be something quite minor. However, as much as I've searched online, I haven't found an answer for this particular problem.
One line: Javascript is Asynchronous.
While many struggle to figure out what it exactly means, a simple example could possibly explain you that.
You request some data from a URL.
When the data from second URL is received, you wish to set a variable with the received data.
You wish to use this outside the request function's callback (after making the request).
For a conventional programmer, it is very hard to grasp that the order of execution in case of JavaScript will not be 1,2 and then 3 but rather 1,3,2.
Why this happens is because of Javascript's event-loop mechanism where each asynchronous action is tied with an event and callbacks are called only when the event occurs. Meanwhile, the code outside the callback function executes without holding on for the event to actually occur.
In your case:
var data;
$(function () {
$.getJSON('https://ipinfo.io', function (ipinfo) {//async function's callback
data = ipinfo;
console.log(data);//first console output
})
})
console.log(data);//second console output
While the async function's callback is executed when the data is received from the $.getJSON function, javascript proceeds further without waiting for the callback to assign the value to the data variable, causing you to log undefined in the console (which is the value of the data variable when you call console.log.
I hope I was able to explain that.!
I have three functions that all work with data from a global object. This global object gets filled with data from a local array in one function and with data from an ajax request with the second function. The third function relies on the data in the object, so the ajax request must have been completed.
I believe I am misunderstanding callbacks. Here's what I do:
var currentCharacter = {}
// this function gets the local data and then calls the second function
function loadData(getMarvelData) {
// do things to fill currentCharacter
getMarvelData(); // this is the callback to the next function (no ?)
}
// this function performs the ajax request, then calls the third function
function getMarvelData(getGoogleMap) {
// do ajax request and add stuff to currentCharacter
getGoogleMap(); // this is the callback to the final function (no ?)
}
function getGoogleMap() {
// do Google Map related stuff with data from currentCharacter
}
I thought setting a function as an argument of another function and then executing it would make the function dependent on the other before it continues. Clearly I still misunderstand callbacks after trying to make it work for a week now. As it is, the getMarvelData function doesn't even get called because I never see the alert popup and the currentCharacter object only has data from the loadData function.
Could somebody show the correct approach for my code, or if my approach of making these three functions is even the right one for this scenario.
The full repository is available at: https://github.com/ChaMbuna/Marvel-Map
V0.9 was actually working but the ajax call was set to run synchronous (it still is btw) Since then I've been overhauling my code to make it work asynchronously and to remove all jQuery (this is a Udacity project and removing jQuery was suggested by an instructor).
Appreciate the help
I have not enough reputation to put a comment, but a wild guess, you should remove the argument in loadData&getMarvelData or actually pass a function in calls to those function.
You have to pass the parameters correctly.
Try this:
var currentCharacter = {}
loadData(getMarvelData, getGoogleMap);
function loadData(f1, f2) {
// do sth.
f1(f2);
}
function getMarvelData(f2) {
// do sth.
f2();
}
function getGoogleMap() {
// do sth.
}
I havn't tested it, but it should work.
I'm sort of a noob with this so please forgive me :)
I can't get this one part of the function to update the variable. Could anyone possibly take a look a see what I'm doing wrong?
http://pastie.org/private/zfnv8v2astglabluo89ta
From line 142 thru 172 I'm not getting any results in the end. I've tested inside that function to make sure it is actually returning data, but the "body" variable is passing back up after line 172. So if I look at my generated HTML on the page, it simply looks the function skips from 140 to 174.
Thanks for any feedback!!
Your $.get is asynchronous. That means it will finish sometime AFTER the rest of the code, thus you won't see it's effect on the body variable inside that function. Instead, it's success callback function will be called long after this function has already finished.
To chain multiple asynchronous ajax calls like you have here, you can't just use normal sequential programming because asynchronous ajax calls aren't sequential. The network request is sent, then your javascript continues executing and SOMETIME LATER when the response arrives, the success handler is called and is executed.
To run sequential ajax calls like you have, you have to nest the work inside the success handler so that the ONLY code that uses the response is actually in the success handler. In pseudo-code, it looks like this:
$.get(..., function(data) {
// operate on the results only in here
// a second ajax function that uses the data from the first
// or adds onto the data from the first
$.get(..., function(data) {
// now finally, you have all the data
// so you can continue on with your logic here
});
// DO NOT PUT ANYTHING HERE that uses the responses from the ajax calls
// because that data will not yet be available here
});
You cannot do what you're doing which is like this:
var myVariable;
$.get(..., function(data) {
// add something to myVariable
});
$.get(..., function(data) {
// add something to myVariable
});
$.get(..., function(data) {
// add something to myVariable
});
// do something with myVariable
None of those ajax calls will have completed before the end of your function. You have to follow a design pattern like in my first example.
For more advanced tools, one can always use jQuery deferreds which are just a different way of defining code to run after an ajax call is done. It looks a little more like sequential programming even though it's really just scheduling code to run the same way my first code example does.
Function 8 will be invoke after line 174-180. You must put code from 174-180 line to the end of function
I am not having any issues with logging in or even calling the api, I just have an issue with getting the response outside of the api callback. I know that it runs asynchronously so I would like to put it in a function that would return the response. Here is my idea
//What I would like to be able to do
function fbUser(){
FB.api('/me', function(response){
//this logs the correct object
console.log(response);
});
//How do I get the response out here?
return response;
}
I would like to call the /me api function once in the beginning and then pass it around to my view objects (I just use the response inside of Backbone Views) and depending on what is needed make other api calls. I currently have certain things working by calling the view from inside of the callback
//What I am doing now, but I lose the ability to pass anything other than the
//the current response to this function/View
FB.api('/me', function(response){
var newView = new facebookView({model: response});
});
I orginally was trying this, but because the api call is asynchronous I had issues with things being undefined
//What I started with but had async issues
var fbResponse;
FB.api('/me', function(response){
fbResponse = response;
});
//I would then try and use fbResponse but it would be undefined
I lose the first response when I make the second. For example my first api call is to /me to get the user info. I can then call /your-fb-id/photos and get photos, but if I make the call to another function inside of the photo api callback I only can reference that response I lost the original /me response. If I could get the response out of the callback then I would be able to pass it as needed. I understand that response is only valid inside of the callback, so how do I make it valid outside of the callback while taking into account it's asynchronousness?
OK everyone I figured this out. It took me a long time and reading many different pages. What I said I wanted to do all deals with callbacks and closures. I will first cover the callback issue. Because the FB.api function is asynchronous you never know when it will return. You can stop Javascript or set a timer, but that is a horrible way to do this. You need a callback. In fact the FB.api is using a callback. That's what the anonymous function as the second parameter is. What I did was create another function that called fbUser and used a callback. Here is what I did:
function startThis() {
var getUser = fbUser(function(model){
console.log(model);
startapp(model);
});
};
function fbUser(callback){
FB.api('/me', function(response){
callback(response);
});
}
The startThis function is called on a positive Facebook auth response. It then calls the fbUser function which has a callback. The callback returns the callback from the FB.api function. Because startThis uses that callback with the return value being called model, the other code will not execute until the callback returns. No more undefined issues. These functions are just wrappers to get the Facebook response to my views. I may have added one too many layers of abstraction, but if you want to pass around the response this is the way.
Secondly I wanted to pass this response on to another view. For example one view loads basic info (using the response from fbUser). I now want to pass that to another view that loads photos (I know this is not best practices MVC, but by using Facebook I don't have much control over the Model). The problem I had though was I couldn't pass the original response to the next view because inside of the callback function for the FB.api call this refers to the Window and not the object I was in. Solution: closures. I won't explain this perfectly, but a closure is a local variable inside of a function that still has a reference inside of an anonymous function. Here is my solution which should illustrate what I am talking about:
photos: function(){
var This = this;
var apiString = '/' + this.model.id + '/photos';
FB.api(apiString, function(response){
loadPhoto(response, 1, This.model);
});
The function loadPhoto is a wrapper to load a photo view (I know backbone can help me with loading different views, but I was tackling one problem at a time). It takes the photo api call as the model, a number as an offset, and the origanl response. The first line in this function sets this to a local variable This. That allows me inside of the anonymous callback function to reference the object this was called from.
I hope this can help someone as I spent a lot of hours and a lot of testing time to find the solution to this. If you don't know about how callbacks or closures work it is hard to find the information you are looking for.