Passing callback functions as arguments in vanilla JS ajax - javascript

I am working on an ajax function to retrieve info from the database. I set up the API and the ajax function does retrieve proper values, but the callback function I passed as an argument won't work on onreadystatechange.
Simplified Code below
function serializeArgs(args) {
//Serialize Arguments
}
function callback(a) { //The function to be called as callback
//Process the response and add contents to the page
}
function getListData(callback) {
var ajaxOptions = {
action: "get_data",
}
var request = new XMLHttpRequest();
request.open("POST", apiurl, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
request.setRequestHeader("Accept-language", "en-US");
request.onreadystatechange = function(event) {
if (this.readyState == 4 && this.status == 200) {
callback(this.response);
}
};
request.send(serializeArgs(ajaxOptions));
}
When I run the function, I get the error "TypeError: callback is not a function"
I have been struggling with this all day, but since I am not that experienced with ajax, I could not figure out why this is happening, and how to solve it. I have a hunch that it has something to do with the asynchronous nature of the ajax, but I'm not sure how I can work around this.

Short reference for this error:
You usually get this error when the following happens:
When a function call is made on a property of that simply isn’t a function.
When a function call is made on an object type that doesn’t contain that function or method.
When a function call is made on a built-in method that expects a callback function argument to be provided, but no such function exists.
I noticed the following in your ajax function:
Your parameter is named callback, just like your function callback. What your ajax function is trying to do is use the parameter callback as a function (not sure if you pass the function callback in as a parameter, if you do, then it's alright, but since I cannot see where you are calling getListData function, I can only guess you are calling it without passing the function callback as an argument).
//Your function callback is trying to access this parameter.
//So unless your actual function callback is being passed in as an argument,
//it's most likely trying to access your parameter even though it is NOT a function or it isn't even receiving a function as parameter
function getListData(callback)
function callback(a)
However if you are trying to simply access it as a function (and not as a callback) i'd recommend changing the parameter name or function name callback.
So to show an example:
First way of doing what you are trying to achieve (callback way).
Call your function this way:
getListData(callback)
Or do the following:
function getListData(changedParamName) {
//this way you can now call your callback function and pass this.response to it
}
That should do the trick.

Where do you call the function getListData?
My guess is, that you call it like getListData() without passing the callback function.
if you call it like getListData(callback) it should work. Hard to say what happens without the full code example.
just for testing you also can change the line
function getListData(callback) {
to
function getListData() {
just to see if it works.
In the second szenario, you don't pass the callback, so when you call the callback function, Javascript will look for it in the parent scope.

Related

How does javascript function call work?

I have this piece of code below:
It makes a GET call to an URL, gets some object, and appends an image to an HTML tag.
function getDataFromApi(searchTerm, callback) {
const URL1 = `some url`;
const design = {
url: URL1,
data: {
"dog breed name": searchTerm
},
type: 'GET',
success: callback
};
$.ajax(design);
}
function displaySearchData(data) {
const allResultsLength = data.message.length;
const ranNum = Math.floor(Math.random() * allResultsLength);
const dogResults = data.message[ranNum];
$(`.js-search-results`).html(`<img src = ${dogResults}>`);
}
function watchSubmit() {
$('.js-search-form').submit(event => {
event.preventDefault();
let queryTarget = $(event.currentTarget).find('.js-query');
let query = queryTarget.val();
queryTarget.val("");
getDataFromApi(query, displaySearchData);
});
}
$(watchSubmit);
I get the getDataFromApi and watchSubmit but getDataFromApi(query, displaySearchData); isn't intuitive to me at all.
I've been writing Java, and it doesn't make sense to me how displaySearchData is getting called without the parameter - it seems that line should be getDataFromApi(query, displaySearchData(data));.
Can someone please explain how this is getting compiled & executed (basically how this is a legitimate syntax) in javascript?
Somewhere in the good'ol jquery, there lies this piece of code:
$.ajax = function(config){
...
// get the response from XHR request,
// and save it in, say, 'response'
...
// now check, if the response is OK 200
// and if so, execute next line
// which is basically - calling your displaySearchData method
config.success(response);
...
}
now, config is your design object, which has a property success which carries the reference to your displaySearchData method.
The data argument of method displaySearchData will now carry the reference to variable response passed in the method invocation config.success(response).
EDIT: the argument callback also carries forward the reference of the method displaySearchData to getDataFromApi
Concept to be noted:
functions can be passed in Javascript as arguments to another function, in which case we only need the referring variable to be passed as argument. Invocation parentheses () are not required.
function A(data){...};
function b(referenceToFunctionA){
...
referenceToFunctionA(someData);
...
};
// correct
b(A);
// wrong, because adding () after any function reference variable
// invokes the method immediately.
// in this particular case the returned value of the method A
// is passed as argument instead of the reference to method A itself.
b(A());
Welcome to JavaScript My Friend. Get ready to experience more magical weirdness as you continue to work on JS. Good luck.
What you need to look at is in the function getDataFromApi().
In that function, you have a "callback" parameter. This parameter is later added into $.ajax. This is a jQuery function that will provide some callback when a certain condition is matched (like before sending a request, when the response has been received,...). This $.ajax callback provide you with 3 parameters, one of them is data (which are being used, textStatus, and jqXHR. Usually, you only need to pay attention to the data since it contains the response from where you are requesting data.
So when the $.ajax success, the "callback" function will be called, which is the displaySearchData. And since $.ajax callback provides you with the data parameter, you can add them to the parameters of displaySearchData. Do note that you can add the extra 2 provided parameters if needed.
You can have a look at that function here: jQuery Ajax

Understanding callback function parameters

I've been trying to understand callback functions and I'm unsure where the data variable/object is actually coming from in this example:
$.get( "ajax/test.html", function( data ) {
console.log(data);
});
I think of this function being defined as something like this:
$.get = function(url, callback) {
// send request to url
callback(data); // execute callback function... but where is data coming from/being defined?
};
I've been trying to understand callback functions and I'm unsure where
the data variable/object is actually coming from in this example:
you invoked jquery function $.get() and gave a handle to your anonymous function as a parameter. jquery did all the work on invoking ajax methods, getting the response (data) and finally invoke your anonymous function (which you passed on to it as parameter). data variable is then passed on to this anonymous function by the same jquery function.

Why is return not working in getJSON and why cant I write into variable from getJSON?

I have a function which uses getJSON but its not working like I expected.
function balbla(name, param) {
$.getJSON("/blabla.json?name=" + name + "&param=" + 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.

Run code after black box AJAX request returns

I have a function which makes an AJAX request to a server and returns relevant information after it completes.
I have another function which manipulates some variables in its namespace based on the returned information.
Currently, I am appending a 'callback' argument to the first function, which is called when the request completes. This, however, blurs the purpose of the first function - instead of being a 'getInfo' function, it's become a 'getInfoAndDo' function.
Ideally, I'd like to call the second function (a 'do' function, which calls the first function, a 'get' function) and does its thing.
I have looked around and found jQuery methods such as .ajaxStop and .ajaxComplete, but they seem to only to work when bound to DOM elements. Is there any way to do this entirely in javascript?
e.g.
function _getEventAttendance(uid, callback) {
var attendQuery = FB.Data.query('SELECT eid,rsvp_status,start_time FROM event_member WHERE uid = {0}', uid);
FB.Data.waitOn( [attendQuery],
function (args){
callback(args[0]);
}
);
}
function logAttendance(attendance){
console.log(attendance);
}
Currently, I am doing:
_getEventAttendance(123456789, logAttendance);
which seems ridiculous to me.
Is there a way to write the code such that I can change the code snippet inside _getEventAttendance / remove the callback argument:
FB.Data.waitOn( [attendQuery],
function (args){
return args[0];
}
);
and then make calls that are equivalently as simple as :
logAttendance.ajaxComplete(_getEventAttendance(123456789));
(I'm just making up the syntax for this, I have no idea how it's supposed to be written.)
$.when(<AJAX Request>).then(function(response){...});
Optionally use $.pipe() to filter response first.

Accessing javascript variables set in Callback function of a PageMethod call

In a click event, I invoke a PageMethods request, which contains two callback functions: OnServerValidationSuccess and OnServerValidationFailure. I also try and set create an object called clientResult;
var clientResult = { rc: "", msg: "" };
$('#btnSave').click( function () {
PageMethods.ServerValidateCreateCompany(companyName, domainName, country, OnServerValidationSuccess, OnServerValidationFailure);
alert(clientResult.rc); //this is null!
});
In the OnServerValidationSuccess, the result parameter contains the asynchronous response. And in there I try to assign it to clientResult.
//On Success callback
function OnServerValidationSuccess(result, userContext, methodName) {
clientResult = result;
if (clientResult.msg.length != 0) {
document.getElementById('resMsg').innerHTML = clientResult.msg;
$('#resMsg').addClass('warning');
}
}
I assign the result to local clientResult variable in the callback function. It should contain the fully formed server response, which is sent correctly by the server-side WebMethod.
However, it remains null or empty after PageMethod is called.
What must I do in order to assign the variable set in the callback to a local variable so that I can access it?
You seem to be assigning the value to clientResult correctly, you're just accessing it too early.
You're making an asynchronous/non-blocking call which means that it returns immediately but continues to work in the background. So your JavaScript immediately returns from the call to PageMethods.ServerValidateCreateCompany and moves on to the alert. But your request hasn't had time to complete and your success callback (OnServerValidationSuccess) hasn't even run yet so you get undefined or null.
You should invoke whatever function is going to process the response in the OnServerValidationSuccess function which is where the response will be received. Perhaps something like this (you already seem to doing something with msg, just move your work with rc here as well)
//On Success callback
function OnServerValidationSuccess(result, userContext, methodName) {
clientResult = result;
alert(clientResult.rc); //this will work
process(clientResult); //process the response here
if (clientResult.msg.length != 0) {
document.getElementById('resMsg').innerHTML = clientResult.msg;
$('#resMsg').addClass('warning');
}
}

Categories