I find a fantastic bug when I use jQuery ajax to do a request to web service:
var AjaxResult;
login = function () {
AjaxResult = "";
$.ajax({
type: "POST",
url: KnutBase.getServiceUrl() + "ServiceInterface/HmsPlannerWebService.asmx/AuthenticateLogin",
data: { username: this.username, password: this.password },
dataType: 'jsonp',
success: function (response) {
//the response value is 'success'
AjaxResult = response;
},
error: function (data, status, e) {
alert("error:" + e);
}
});
//alert(AjaxResult);
//if i do not alert a message here, this function will return a null value
//however, i do alert here, then i get a 'success' value.
return AjaxResult;
}
How could this happen.
I am confused as to why the AjaxResult returns its value before the ajax method set response value to it.
Ajax is asynchronous call to your method and it do the processing in the back for server trip. You will not get the result until the success method is called. You can read more about jquery ajax here
You can set async attribute to false of ajax() to make a synchronous call.
EDIT: Using deferreds --
$.Deferred can be used to run code after some other code has completed. Ajax is an excellent example, and all of the jQuery ajax methods (except .load) implement the $.Deferred interface. You can use $.when to watch deferreds. For example:
login = function () {
return $.ajax({...
The deferred gets returned from the method above, so you can do the following:
$.when(login()).done(function (response) { ... });
Any code that needs to be synchronized with the ajax response has to go in the .done callback.
Note that this is not much different than just using the success callback with $.ajax. The point is that all of your work that relies on what is returned from Ajax needs to be done in these callbacks.
Since ajax is asynchronous, return AjaxResult is actually executed before the ajax result completes. You can not return in this way; you need to perform some action in the callback of the ajax requests or use deferred.
It has to do with the fact that AJAX is asynchronous (that's actually the meaning of the first A in it). alert stops the code until you press the button, and the request has time to complete. With no alert, the function proceeds immediately, the AJAX has barely begun its work, and of course the value is still empty.
Generally, you can't have functions return values that they get from AJAX. Set up a callback for it, and deal with it when it comes.
How can Ajax do asynchronous request and return a synchronous result
You can't. It's just impossible. If your code really tries to do that, it will fail.
Related
I run into an issue when trying to use the request method in javascript where I can't save a value. I'll run a block of code like:
let savedData;
request({
url: url,
json: true
}, function (err, resp, body) {
if (err) {
return;
}
savedData = body.data;
});
console.log(savedData);
I know that request doesn't block or something, so I think it's run after the console.log or something like that? I just need to know how I can save the desired data for use later in the method.
Your code is working correctly, you're just neglecting the fact that the callback provided as a second parameter to request() is executed asynchronously.
At the time when your console.log() is executed, the network request may or may not have successfully returned the value yet.
Further Explanation
Take a look at the documentation for the request() function.
It states that the function call takes the following signature,
request(options, callback);
In JavaScript, a callback performs exactly as the name perscribes; it calls back by executing the provided function, after it has done what it first needs to.
This asynchronous behavior is especially prominent in making networking requests, since you wouldn't want your program to freeze and wait for the network request to retrieve or send what you've requested.
Example
function callback() {
console.log('I finished doing my asynchronous stuff!');
console.log('Just calling you back as I promised.');
}
console.log('Running some asynchronous code!');
request({...options}, callback);
console.log('Hi! I'm being called since I'm the next line of code; and the callback will be called when its ready.');
Output
Running some asynchronous code!
Hi! I'm being called since I'm the next line of code; and the callback
will be called when its ready.
I finished doing my asynchronous stuff!
Just calling you back as I promised.
You'll either need to do the rest of the code in the callback of the request function, or use a promise. Anything outside of that callback is going to execute before saveedData ever shows up.
Im building a browser based application.
On window.load, i call a jquery AJAX GET function which is supposed to get me some gamedata from the server.
Once the data is there, i parse this data into my javascript and build some stuff up.
For that reason, i set async to false.
Now looking at this function, i want to call another function on ajax.success, but i want to use the freshly returned data as well as the data that was sent to this very function as a parameter (from another async ajax call).
How can i do this ?
thanks
window.onload = function(){
ajax.getFleets();
ajax.getShips(fleets);
manager.draw() // can only draw if both ajax functions have received data !)
}
getShips: function(fleets){
$.ajax({
type: "GET",
url: "getGameData.php",
datatype: "json",
data: {
type: "ships"
},
error: ajax.error,
success: ajax.mergeData, //want to pass fleets and returnData to this function
async: false
});
},
Also, on a general note, using async: false is the correct approach for this, right ?
thanks,
$.ajax will pass your callback function the result of the AJAX request, so you can use a simple anonymous function to get both parts in. Let's say you have your callback function defined like this:
ajax = {};
ajax.mergeData = function(fleets, ajaxResult) {
console.log(fleets);
console.log(ajaxResult);
};
You can define your callback function as:
function(result) { ajax.mergeData(fleets, result); }
Putting that all together, your getShips function will look like this:
ajax.getShips = function(fleets) {
$.ajax({
type: "GET",
url: "getGameData.php",
datatype: "json",
data: {
type: "ships"
},
error: ajax.error,
success: function(result) { ajax.mergeData(fleets, result); }
});
};
To answer your second question: async: false is a deprecated option, so you can't rely on it to work. It will also lock up the browser until the server responds, which makes for a really bad user experience. It's far better to use callback functions or promises to carry out an action when your AJAX requests complete. Here's a short summary of the promises concept that might be useful.
Which leaves the last, big question: how do we chain these calls together so getShips() depends on the result of getFleets(), and manager.draw() depends on both? With just two calls, you could get away with just using callbacks; getShips' success callback invokes getFleets, and getFleets' callback invokes manager.draw. This is the easiest way to get an A → B → C dependency chain working – but we could also use jQuery's promises, which might make our code easier to follow.
First, let's change getFleets to return the result of calling $.ajax:
ajax = {};
ajax.getFleets = function() {
return $.ajax({ /* Details elided here */ });
}
Next, let's update getShips to make an AJAX request for that data, and return a promise that returns the combined data:
ajax.getShips = function(fleets) {
return $.ajax({ /* Details elided again */ })
.done(function(ajaxResult) { return mergeData(fleets, ajaxResult); });
};
This is a bit trickier; $.ajax returns a promise that will eventually resolve to our ship data. When it does, we'll call the function in .done that returns the merged data. And that .done also returns a promise – so getShips is now returning a promise that will eventually return the merged data.
So now we can write our window.onload function to chain all these together, and only call manager.draw when all the data's available:
window.onload = function() {
ajax.getFleets()
.done(ajax.getShips)
.done(manager.draw);
}
I have a function which I need to run after several Ajax calls are complete. Each AJAX call is currently called one after another and currently do work within their respective callbacks. Is this a candidate for a promise chain as each ajax call only runs if its respective test is true? And I don't necessarily need these calls to be serially run just that I get one callback at the end (when all the Ajax calls which fulfilled the criteria to be run completed their respective callbacks).
if(MyTest1){
RunMyAjaxCall1(param1,param2,successAjaxCallback1,failureAjaxCallback1);
}
if(MyTest2){
RunMyAjaxCall2(param1,param2,successAjaxCallback2,failureAjaxCallback2);
}
if(MyTest3){
RunMyAjaxCall3(param1,param2,successAjaxCallback3,failureAjaxCallback3);
}
if(MyTest4){
RunMyAjaxCall4(param1,param2,successAjaxCallback4,failureAjaxCallback4);
}
Sample AJAX Call
RunMyAjaxCall1 = function (param1,param2, successCallback, failureCallback) {
$.ajax({
type: "POST",
datatype: 'json',
url: "/WebServices/GlobalWebService.asmx/webservicemethod1",
data: JSON.stringify({ param1: param1, param2: param2 }),
contentType: 'application/json; charset=utf-8',
success: function (data) { successCallback(data.d); },
error: function (data) { failureCallback(data.d) }
});
}
You could use this:
var requests = [];
if (MyTest1) {
requests.push(RunMyAjaxCall1(param1,param2,successAjaxCallback1,failureAjaxCallback1));
}
// ...continue with other if statements
$.when.apply(null, requests).always(function () {
// All handlers used are done (either succeeded or failed)
});
Since you said you were making AJAX calls, I assume you're using $.ajax. The value returned from calling $.ajax is a Promise. The RunMyAjaxCallX functions need to return this in order for this scheme to work.
Using requests allows for a dynamic way to group requests. The if statements determine which are executed and included.
The use of $.when (with passing requests as an argument) creates a new Promise, encompassing all requests that you determined should be included. Then using the .always() method, it will run when all included requests are completed.
Here's an example of it working: http://jsfiddle.net/vmw8H/
Some reference:
http://api.jquery.com/jQuery.when/
http://api.jquery.com/deferred.always/
I'm new to Coffeescript, and I have a question about Ajax.
jQuery ->
api =
getId: ->
res = []
$.ajax
dataType: "jsonp"
url: "http://localhost:3004/videos.json"
success: (data) =>
if data
data.forEach (elem) =>
embed_id = elem.video_id
res.push(embed_id)
console.log res
return res
I try this code, then
console.log res
the output is
["id1","id2","id3",...] .
So I expect api.getId() to return ["id1","id2","id3",...]
Instead, I see
Object
-> abort
function(a) {...}
...
-> status: 200
...
-> success: function (){...}
in my debug window.
I want to return the value of the response.
This is not really a Coffeescript problem, this is just how asynchronous requests work. You cannot immediately return the result from the XHR call, you have to define a callback that get receive the result (or the error) once that call completes.
Take a look at what your code compiles to (as Javascript).
getId: ->
## .. snip ..
$.ajax ## .. snip ..
You have a function getId, which returns the return value of $.ajax, which is (according to the jQuery spec) an XHR object, not the result of the success callback. You could use it to check on the progress, abort the request, set more options and so on.
success: (data) =>
## .. snip ..
return res
There is no point in returning anything from an XHR success callback. You need to work with the data right here, for example putting it into the DOM somewhere, or calling another function that does something useful with it.
The return res statement is inside AJAX call. It does not return from getId() function, but inner AJAX callback. You cannot do this like this. AJAX calls are asynchronous, while you want them do be synchronous. I would advice you to do something like this:
jQuery ->
api =
getId: (callback) ->
res = []
$.ajax
dataType: "jsonp"
url: "http://localhost:3004/videos.json"
success: (data) =>
if data
data.forEach (elem) =>
embed_id = elem.video_id
res.push(embed_id)
callback(res)
else
callback(null)
error: =>
callback(null)
and now in code you can use
api.getId(function(res) {
// do something with the result.
});
// some other things
Keep in mind that the some other things code may (and will) be called before the do something with the result code.
Sorry about mixing CoffeeScript with JavaScript but I'm not really into CoffeeScript.
The following code always returns "undefined"
function sendCommand(cmdJson){
chrome.extension.sendRequest(cmdJson, function(response){
return response;
});
}
I've also tried this variant with the same result
function sendCommand(cmdJson){
var msg;
chrome.extension.sendRequest(cmdJson, function(response){
msg = response;
});
return msg;
}
If I do an alert(response); instead of return response; I get the expected value.
I'm guessing that chrome.extension.sendRequest is asynchronous, in which case sendCommand doesn't return anything. The response-handler inside sendCommand is the one that returns something, but that's not the same, as sendCommand returning something. So when you call sendCommand it returns undefined.
Basically, sendCommand invokes the chrome.extension.sendRequest function, and then returns undefined right away, while the chrome.extension.sendRequest function is still running.
There's no real way to make something asynchronous behave synchronously - it's better to restructure your code.
It is most likely because you are performing an ajax call, which is asynchronous by nature. An asynchronous function cannot return a value since there is no way to know when the asynchronous call will complete.
If you are using jQuery, you have two alternative options to get around this problem. If you are using jQuery 1.5 or later, then you can use Deferred Objects. Deferred Objects are a new object that jQuery allows you to use to interact with asynchronous code.
By default, a jquery ajax function (i.e. $.ajax, $.get, $.post, $.getJSON) returns a Deferred Object, so all you have to do is store your ajax call in a variable and then call available methods listed in the jQuery Deferred Object api. Here is one of the best tutorials I have seen on jQuery Deferred Objects. http://www.erichynds.com/jquery/using-deferreds-in-jquery/
var sendResponse = $.get("/getResponse");
//Somewhere else in your code
sendResponse.success(function(response) {
return response;
});
The other options is to make your jquery ajax call synchronous. You can do this by setting the async ajax property to false.
$.ajax({
url: "someURL",
async: false
});