This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 9 years ago.
I'm having trouble with callbacks mainly because I don't understand how they're working (or supposed to work).
I have my function:
function checkDuplicateIndex(values, callback) {
$.ajax({
type: "POST",
url: url,
data: "command=checkIndexAlbumTracks&" + values,
dataType: "html",
success: function(data){
var returnValue = data.d;
callback(returnValue);
}
});
}
And then within a submit event, how do I properly call checkDuplicateIndex so that I can alert() the value?
This ended up being a long answer, so I'm going to try to split it into pieces.
Functions in Javascript
So within javascript, a function is an object that can be passed around, assigned to a variable, etc, just like any other data type. The difference is that a function, rather than being a string of text, number, etc, is a block of code waiting to be executed.
This is often confusing to people starting out with programming because usually when you write code, it is executed when you run the program. But for functions, this is not the case. When you write code inside a function, it waits there not executing until you call the function. If you do not call the function, the code is never executed. Let's check out a quick example:
function say_hello(){
console.log('hello!');
}
What you see here is called a function declaration. This means you are creating a function, which is a block of code waiting to be executed. If you run this code, nothing will be logged to the console. Now let's look at a function call.
function say_hello(){
console.log('hello!');
}
say_hello();
So here we declare the function just like before, but below we call it. A function call is just the name of the function followed by open and close parens. If the function takes arguments, they will be inside the parens, but no need to worry about that for now. If you were to run this code, you would in fact see hello! logged to the console, because the function was called, which executes the code inside.
Asynchronous Code
Now, let's switch gears for a second. When you make a jquery ajax call, jquery abstracts a lot of code into the library. They take care of setting up the XMLHttpRequest, firing it off to the place you specify, and collecting the result, and they do this in a way that works cross-browser. But since javascript is asynchronous, as soon as the ajax call goes off, javascript keeps executing code after the ajax call, because who wants to wait around for someone else's server to respond while you could be still getting in that work. So if you fire off something like this:
$.ajax({
url: 'http://google.com',
success: function(){ console.log('done!') }
});
console.log('after ajax call');
...you may be surprised to find that it logs after ajax call before logging done!. This is because, as stated earlier, in javascript calls that deal with I/O are often asynchronous.
So if the ajax call is made and it immediately continues executing code even if the ajax call has not finished, how can we specify code that will run when it's finished? This is where everything comes together. By providing jquery with a function, which as we remember is a block of unexecuted code, we can provide a way for ourselves to write code that is executed only after the ajax call has finished by passing the block of unexecuted code to jquery and saying essetially "hey jquery, take this code, and when the ajax call is finished, call it and pass in any data you got out of it." How convenient!
The way we do this is through the success and error properites of jquery's ajax function. If the request was successful, it will call the function we pass to success, and I assume you can guess what happens if there was an error.
Putting It All Together
Asynchronous code and first class functions are two of the most confusing parts about javascript, and once you understand these two concepts, you'll be in a great spot, although it may take a while to get there. So it's important to think carefully about it and experiment. Let's talk through a couple ways to handle the example you are working with here, about jquery ajax.
First, we can try making our own function and passing the name of the function to the success handler. Then when it comes back, it will call the function. Let's take a look:
var my_callback = function(data){
console.log(data);
}
$.ajax({
url: 'http://google.com',
success: my_callback
});
This is an interesting way of doing it. Here we have assigned an anonymous function to a variable, then passed the variable name to the success handler. This will work fine. Now let's try another way:
function my_callback(data){
console.log(data);
}
$.ajax({
url: 'http://google.com',
success: my_callback
});
Here, we define a named function and do the same thing. This will work the same way. Named functions in javascript can actually be declared after the are used, so you could move the function declaration below the ajax call and it would still work. Try this out. This is a nice advantage to named functions.
Finally, let's take a look at a third way we could handle it:
$.ajax({
url: 'http://google.com',
success: function(data){
console.log(data);
}
});
Here, we define an anonymous function right inline on the success handler. This works exactly the same as either of the other two options. In all three of these ways, jquery receives a function declaration, and calls it when it needs to, which is after the ajax request has come back.
I know this is a super long answer, but what you are confused about here are some of the core concepts of javascript, and I thought it would be more helpful to go over them here than to just solve your problem and give you the answer without explanation of the concepts. In fact, I haven't actually tackled your problem here at all, but you will easily be able to solve it yourself after understanding these concepts. If you are still having trouble, drop a comment and I'll try to clarify more.
Given the above code, you would call it like this within your submit handler:
var values = '…';
checkDuplicateIndex(values, function(returnValue) {
alert(returnValue);
// additional processing here...
});
Related
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'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'm having issues getting a variable declared in an .click function to be updated in a Get function within the click function. I've gathered that even though the variable has the same name, within the Get function it is really declaring it anew.
I've tried to find examples helping me, but it appears to me that the Get method is such a specialized function that the examples didn't seem to apply.
I would like the value of 'SettingContentToEdit' to get updated with information retrieved with the Get function.
Below is the code.
Thank you for your help!
$(".cellSetting").click(function () {
var clickedClass = $(this).attr("class");
var clickedItemID = $(this).attr("id")
var SettingContentToEdit = "not changed";
var JSONSend = {
'ItemName': clickedItemID, //send the item name so the model knows which one to pull up!
'ItemSetting': clickedClass
};
$.get(
'/Home/getItem',
JSONSend,
function (data) {
// $('#dialog').html(data.ItemSettings[data.SettingToEdit]);
SettingContentToEdit = data.ItemSettings[data.SettingToEdit];
alert(SettingContentToEdit); //returns the correct info
}
);
alert(SettingContentToEdit); //returns "not changed"
});
Your issue is that your ajax call is asyncronous. The success handler for the get() function is called some time after your click handler and the alert() has already completed. You can refer to the local variables in your success handler, but the code that follows the success handler executes BEFORE the success handler does.
This is because your get() operate is asynchronous. Calling it just STARTS the networking operation and then your javascript execution continues (while the networking operation works in the background). After starting the networking operation, your alert() is called. Then, some time LATER, the Ajax call completes and the success handler is executed.
This is a very common mistake and has indeed been asked and answered hundreds (if not thousands) of times here on SO (I've personally probably answered 20-30) so you are not alone in missing this understanding at first.
If you want to use the results of the ajax operation, then you need to put the code that does that either in your success handler or put it in a function that you call from your success handler. You cannot put it after the get() function because it will execute too soon before the results are known.
AJAX is asynchronous. If you check SettingContentToEdit a second or so later than you are doing, you'd see the value has updated.
Either put your code inside the get function (where you have your alert showing the correct value) or make the request synchronous (you'll have to look up the jQuery docs because I don't use jQuery).
This may be more of a scoping question. I'm trying to set a JSON object within a $.getJSON function, but I need to be able to use that object outside of the callback.
var jsonIssues = {}; // declare json variable
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// jsonIssues not accessible here
A similar question like this one was asked in another post, and the consensus was that anything I need to do with the JSON objects needs to be done within the callback function, and cannot be accessed anywhere else. Is there really no way that I can continue to access/manipulate that JSON object outside of the $.getJSON callback? What about returning the variable, or setting a global?
I'd appreciate any help. This just doesn't seem right...
UPDATE:
Tried setting the $.ajax() async setting to false, and running through the same code, with no luck. Code I tried is below:
var jsonIssues = {}; // declare json variable
$.ajax({ async: false });
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// jsonIssues still not accessible here
Also, I've had a couple responses that a global variable should work fine. I should clarify that all of this code is within $(document).ready(function() {. To set a global variable, should I just declare it before the document.ready? As such:
var jsonIssues = {};
$(document).ready(function() {
var jsonIssues = {}; // declare json variable
$.getJSON("url", function(data) {
jsonIssues = data.Issues;
});
// now accessible?
}
I was under the impression that that a variable declared within document.ready should be "globally" accessible and modifiable within any part of document.ready, including subfunctions like the $.getJSON callback function. I may need to read up on javascript variable scoping, but there doesn't seem to be an easy to achieve what I'm going for. Thanks for all the responses.
UPDATE #2:
Per comments given to answers below, I did use $.ajax instead of .getJSON, and achieved the results I wanted. Code is below:
var jsonIssues = {};
$.ajax({
url: "url",
async: false,
dataType: 'json',
success: function(data) {
jsonIssues = data.Issues;
}
});
// jsonIssues accessible here -- good!!
Couple follow-up comments to my answers (and I appreciate them all). My purpose in doing this is to load a JSON object initially with a list of Issues that the user can then remove from, and save off. But this is done via subsequent interactions on the page, and I cannot foresee what the user will want to do with the JSON object within the callback. Hence the need to make it accessible once the callback complete. Does anyone see a flaw in my logic here? Seriously, because there may be something I'm not seeing...
Also, I was reading through the .ajax() jQuery documentation, and it says that setting async to false "Loads data synchronously. Blocks the browser while the requests is active. It is better to block user interaction by other means when synchronization is necessary."
Does anyone have an idea how I should be blocking user interaction while this is going on? Why is it such a concern? Thanks again for all the responses.
$.getJSON is asynchronous. That is, the code after the call is executed while $.getJSON fetches and parses the data and calls your callback.
So, given this:
a();
$.getJSON("url", function() {
b();
});
c();
The order of the calls of a, b, and c may be either a b c (what you want, in this case) or a c b (more likely to actually happen).
The solution?
Synchronous XHR requests
Make the request synchronous instead of asynchronous:
a();
$.ajax({
async: false,
url: "url",
success: function() {
b();
}
});
c();
Restructure code
Move the call to c after the call to b:
a();
$.getJSON("url", function() {
b();
c();
});
Remember that when you supply a callback function, the point of that is to defer the execution of that callback until later and immediately continue execution of whatever is next. This is necessary because of the single-threaded execution model of JavaScript in the browser. Forcing synchronous execution is possible, but it hangs the browser for the entire duration of the operation. In the case of something like $.getJSON, that is a prohibitively long time for the browser to stop responding.
In other words, you're trying to find a way to use this procedural paradigm:
var foo = {};
$.getJSON("url", function(data) {
foo = data.property;
});
// Use foo here.
When you need to refactor your code so that it flows more like this:
$.getJSON("url", function(data) {
// Do something with data.property here.
});
"Do something" could be a call to another function if you want to keep the callback function simple. The important part is that you're waiting until $.getJSON finishes before executing the code.
You could even use custom events so that the code you had placed after $.getJSON subscribes to an IssuesReceived event and you raise that event in the $.getJSON callback:
$(document).ready(function() {
$(document).bind('IssuesReceived', IssuesReceived)
$.getJSON("url", function(data) {
$(document).trigger('IssuesReceived', data);
});
});
function IssuesReceived(evt, data) {
// Do something with data here.
}
Update:
Or, you could store the data globally and just use the custom event for notification that the data had been received and the global variable updated.
$(document).ready(function() {
$(document).bind('IssuesReceived', IssuesReceived)
$.getJSON("url", function(data) {
// I prefer the window.data syntax so that it's obvious
// that the variable is global.
window.data = data;
$(document).trigger('IssuesReceived');
});
});
function IssuesReceived(evt) {
// Do something with window.data here.
// (e.g. create the drag 'n drop interface)
}
// Wired up as the "drop" callback handler on
// your drag 'n drop UI.
function OnDrop(evt) {
// Modify window.data accordingly.
}
// Maybe wired up as the click handler for a
// "Save changes" button.
function SaveChanges() {
$.post("SaveUrl", window.data);
}
Update 2:
In response to this:
Does anyone have an idea how I should be blocking user interaction while this is going on? Why is it such a concern? Thanks again for all the responses.
The reason that you should avoid blocking the browser with synchronous AJAX calls is that a blocked JavaScript thread blocks everything else in the browser too, including other tabs and even other windows. That means no scrolling, no navigation, no nothing. For all intents and purposes, it appears as though the browser has crashed. As you can imagine, a page that behaves this way is a significant nuisance to its users.
maybe this work, works to me.. :)
$variable= new array();
$.getJSON("url", function(data){
asignVariable(data);
}
function asignVariable(data){
$variable = data;
}
console.log($variable);
Hope it help you..
:)
You could approach this with promises:
var jsonPromise = $.getJSON("url")
jsonPromise.done(function(data) {
// success
// do stuff with data
});
jsonPromise.fail(function(reason) {
// it failed... handle it
});
// other stuff ....
jsonPromise.then(function(data) {
// do moar stuff with data
// will perhaps fire instantly, since the deferred may already be resolved.
});
It is pretty straight forward and a viable way to make async code feel more imperative.
"But this is done via subsequent interactions on the page, and I cannot foresee what the user will want to do with the JSON object within the callback."
The callback is your opportunity to set the screen up for the user's interaction with the data.
You can create or reveal HTML for the user, and set up more callbacks.
Most of the time, none of your code will be running. Programming an Ajax page is all about thinking about which events might happen when.
There's a reason it's "Ajax" and not "Sjax." There's a reason it's a pain to change from async to sync. It's expected you'll do the page async.
Event-driven programming can be frustrating at first.
I've done computationally intensive financial algorithms in JS. Even then, it's the same thing--you break it up into little parts, and the events are timeouts.
Animation in JavaScript is also event driven. In fact, the browser won't even show the movement unless your script relinquishes control repeatedly.
You are just running into scoping issues.
Short answer:
window.jsonIssues = {}; // or tack on to some other accessible var
$.getJSON("url", function(data) {
window.jsonIssues = data.Issues;
});
// see results here
alert(window.jsonIssues);
Long answers:
Scoping issue in Javascript
Javascript closure scoping issue
Is there a way to listen for a javascript function to exit? A trigger that could be setup when a function has completed?
I am attempting to use a user interface obfuscation technique (BlockUI) while an AJAX object is retrieving data from the DB, but the function doesn't necessarily execute last, even if you put it at the end of the function call.
Example:
function doStuff() {
blockUI();
ajaxCall();
unblockUI();
};
Is there a way for doStuff to listen for ajaxCall to complete, before firing the unBlockUI? As it is, it processes the function linearly, calling each object in order, then a separate thread is spawned to complete each one. So, though my AJAX call might take 10-15 seconds to complete, I am only blocking the user for just a split-second, due to the linear execution of the function.
There are less elegant ways around this...putting a loop to end only when a return value set by the AJAX function is set to true, or something of that nature. But that seems unnecessarily complicated and inefficient.
However you're accomplishing your Ajax routines, what you need is a "callback" function that will run once it's complete:
function ajaxCall(callback){
//do ajax stuff...
callback();
}
Then:
function doStuff(){
blockUI();
ajaxCall(unblockUI);
}
Your AJAX call should specify a callback function. You can call the unblockUI from within the callback.
SAJAX is a simple AJAX library that has more help on how to do AJAX calls.
There's also another post that describes what you're looking for.
You can do a synchronous xhr. This would cause the entire UI block for the duration of the call (no matter how long it might take).
You need to redesign your program flow to be compatible with asynchronus flow, like specifying a callback function to be called after the response is processed. Check out how Prototype or JQuery or ... accomplishes this.
The answer is simple, you have to call unblockUI() when your ajax request returns the result, using jQuery you can do it like this:
function doStuff(){
blockUI();
jQuery.ajax({
url: "example.com",
type: "POST", //you can use GET or POST
success: function(){
unblockUI();
}
});
}
It sounds to me that you want the user to wait while info is being fetched from the db. What I do when I make an Ajax call for some info from the database is to display an animated gif that says "getting it..." - it flashes continually until the info is retrieved and displayed in the webpage. When the info is displayed, the animated gif is turned off/hidden and the focus is moved to the new info being displayed. The animated gif lets the user know that something is happening.