I have four functions that handle a certain part of my app for getting a photo url.
In the function handleGetPhotoResponse the alert has my url in it, everything looks like it should.
The problem is in the function handleGetUsersFurKidsResponse. the variable "fkimg" is undefined. Can anyone tell me where I went wrong?
To use this part of the app I make a call to "getUsersFurKids".
function handleGetPhotoResponse(responseText, size) {
var photoDetails = JSON.parse(responseText);
var thePhoto = photoDetails[size];
alert(thePhoto);
return thePhoto;
}
function getPhoto(id, size) {
var url = "url-removed"+id;
var request = new XMLHttpRequest();
//Send the proper header information along with the request
request.open("GET", url);
request.onload = function() {
if (request.status == 200) {
return handleGetPhotoResponse(request.responseText, size);
}
};
request.send(null);
}
// function to handle the response of getting a users fur kids
function handleGetUsersFurKidsResponse(responseText) {
var ul = document.getElementById("furKidList");
var furKids = JSON.parse(responseText);
for(var i = 0; i<furKids.length; i++){
var li = document.createElement("li");
var fkimg = getPhoto(furKids[i].ui_id, 'small');
li.innerHTML = "<a href=\"\"><img src=\""+fkimg+"\"> "+furKids[i].p_name;
ul.appendChild(li);
}
}
// function to get a users fur kids
function getUsersFurKids(id) {
// api url for getting fur kids
var url = "url-removed"+id;
var request = new XMLHttpRequest();
//Send the proper header information along with the request
request.open("GET", url);
request.onload = function() {
if (request.status == 200) {
handleGetUsersFurKidsResponse(request.responseText);
}
};
request.send(null);
}
// receive a callback---v
function getPhoto(furKid, size, callback) {
// ^---and the current furKid
// use the ui_id------------v
var url = "url-removed" + furKid.ui_id;
var request = new XMLHttpRequest();
request.open("GET", url);
request.onload = function() {
if (request.status == 200) {
// Invoke the callback, and pass it the result of handleGetPhotoResponse
callback(handleGetPhotoResponse(request.responseText, size), furKid.p_name);
// use the p_name-----------------------------------------^
}
};
request.send(null);
}
function handleGetUsersFurKidsResponse(responseText) {
var ul = document.getElementById("furKidList");
var furKids = JSON.parse(responseText);
for(var i = 0; i<furKids.length; i++){
// pass a callback-----------------v
getPhoto(furKids[i], 'small', function(fkimg, p_name) {
var li = document.createElement("li");
li.innerHTML = "<a href=\"\"><img src=\""+fkimg+"\"> "+ p_name;
ul.appendChild(li);
});
}
}
You don't seem to understand that getPhotos is an asynchronous function. The onload handler inside it is called sometime LATER long after the getPhoto function itself returns and is finished. As such, you CANNOT return a value from getPhoto that was retrieved in onload.
Instead, you have to put all code that needs to use the onload response from getPhoto inside of the onload handler itself (or called from inside the onload handler) because ONLY when the onload handler is called is that data known.
This is how asynchronous programming works in javsacript.
Add a callback to getPhoto and call that callback from inside the onload handler, passing it the img that you got from the async call. Then, and only then, can you use that img data.
Related
Is it possible for me to call a function then override the contents of the variable before actually running it?
So I have a function that basically pulls in my Git profile like this:
var GetGitInfo = function() {
var xhr = new XMLHttpRequest();
var gitURL = "https://api.github.com/users/myself/repos";
xhr.open("GET", gitURL);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
}
Then I call the function in another step by doing GetGitInfo(); which all works fine.
However, If I wanted to call the function and replace the gitURL variable how would I achieve that?
So something like
GetGitInfo(
gotURL= "https://api.github.com/users/new_user/repo";
);
You can't modify a local variable to a function from outside the function. They are private to the function's implementation.
But, since it's your own function, you can just create an argument that can be passed into the function. You can even make the argument optional so it will take your initial value as the default value if it is not passed.
var GetGitInfo = function(url) {
var xhr = new XMLHttpRequest();
var gitURL = url || "https://api.github.com/users/myself/repos";
xhr.open("GET", gitURL);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
}
Then, you can use the function the way you were using it or you can pass in an URL to use:
getGitInfo(); // uses your default URL
getGitInfo("http://someURL"); // uses the URL you pass in
FYI, this function looks like it will ultimately need to either return a promise or accept a callback so you can communicate the results back to the caller.
From the snippet above you need to set the url as a function parameter so when calling it uses the specified url.
var GetInfo = function(url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
GetInfo("https://api.github.com/users/myself/repos");
You should do a toString() on the function:
GetGitInfo.toString()
Then you should do a text search and replace on the variable and it's data:
GetGitInfo.toString().substring(0,GetGitInfo.indexOf('somestring'))+'gitUrl="newURL"'+GetGitInfo.toString().substring(.......)
Then you should eval that string!
Or, you know, use function parameters. Either way. Whatever's easiest.
Pass a parameter to the function:
var GetGitInfo = function(gitURL) {
var xhr = new XMLHttpRequest();
xhr.open("GET", gitURL);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
}
GetGetInfo("https://api.github.com/users/myself/repos");
Just add a parameter to your function:
var GetGitInfo = function(gitURL) {
var xhr = new XMLHttpRequest();
xhr.open("GET", gitURL);
xhr.send(null);
xhr.onreadystatechange = function() {
var DONE = 4; // readyState 4 means the request is done.
var OK = 200; // status 200 is a successful return.
if (xhr.readyState === DONE) {
if (xhr.status === OK) {
// console.log(xhr.responseText);
console.log(JSON.parse(xhr.responseText));
} else {
console.log('Error: ' + xhr.status);
}
}
};
}
and call it like this:
GetGitInfo("https://api.github.com/users/myself/repos");
Use the parameters
var getData = function(url){
// url can be used here
}
var data = getData("http://apiurl.xy")
I am trying to read a local file on the server with a standard function loadDoc(url, cfunc), then
1) search for a particular string in the file (getLine());
2) if possible, store that line to a variable.
For point 1 I pass a string to the callback.
2) Getting the response is problematic because XMLHTTPRequest is asynchronous. At this moment the error is:
"ReferenceError: xhttp is not defined"
function main(){
var url="data.txt"
var str="1,0,"; //just an example
var myCallBackWithVar = function(){
getLine(str);
};
loadDoc(url, myCallBackWithVar);
//Can I get the line here somehow?
}
function loadDoc(url, cfunc) {
var xhttp=new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
cfunc(xhttp);
}
}
xhttp.overrideMimeType('text/plain');
xhttp.open("GET", url, true);
xhttp.send();
}
//Find string with the desired data in txt file
function getLine(str) {
var data=xhttp.responseText;
//Find the line from the txt file
var start=data.indexOf(str);
var end=data.indexOf(";",start);
var line=data.substring(start,end);
return line;
}
data.txt is something like this:
some data here
0,0,9;
1,0,10;
1,1,11;
I have already tried to pass the XMLHTTPRequest objetct getLine(xhttp,str). How to solve points 1 and 2? I'd rather keep it jQuery free for the moment. Thanks
Can I get the line here somehow?
I don't think that's a good idea. You can't be sure that your app will work correctly. XHR is a async function and you should use async architecture.
Here the example how this functionality can be done.
var text; // define global variable
var str = "1,0,"; //just an example
function main(){
var url = "data.txt";
var cb = function (data){
text = getLine(data);
// you can use text var here
// or in anyewhere in your code
}
loadDoc(url, cb);
}
function loadDoc(url, cb) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
cb(xhr.responseText);
}
}
xhr.overrideMimeType('text/plain');
xhr.open("GET", url, true);
xhr.send();
}
//Find string with the desired data in txt file
function getLine(data) {
if(data) {
//Find the line from the txt file
var start = data.indexOf(str);
var end = data.indexOf(";", start);
var line = data.substring(start, end);
return line;
}
}
On complete, you don't need to pass the whole xhttp variable through too the callback function. When you do this:
function getLine(str) {
var data=xhttp.responseText;
xhttp is already out of scope. To fix this, the parameter name would also have to be xhttp.
A better way would be to do :
cfunc(xhttp.responseText);
and then
var data=str
This way, you are passing only what you need as an argument.
I've been working on a single page site in which data from a single json file is rendered variously in different sections - nothing displayed on load, but only upon click event. I learned a little about callbacks getting that wired up, but as I almost completed it I realized how flawed the concept was, so now I'm back to the drawing board.
My idea now is to make the ajax call onload, set the json result as a variable available to several functions. I thought it would go something like this:
window.onload = function() {
var myJsonData = function() {
var request = new XMLHttpRequest();
request.open("GET", "/json/someJsonData.json", true);
request.setRequestHeader("content-type", "application/json");
request.send(null);
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
var myJsonString = JSON.parse(request.responseText);
var myJsonArray = myJsonString["Projects"];
return myJsonArray;
}
}
} // onreadystatechange
} // var myJsonData
myJsonData(); // *
console.log("myJsonArray: " + myJsonArray); // undefined
Or:
window.onload = function() {
myJsonData();
}
function myJsonData() {
var request = new XMLHttpRequest();
request.open("GET", "/json/someJsonData.json", true);
request.setRequestHeader("content-type", "application/json");
request.send(null);
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
var myJsonString = JSON.parse(request.responseText);
var myJsonArray = myJsonString["Projects"];
alert("you are here (myJsonArray defined)");
alert("myJsonArray: " + myJsonArray);
console.log("myJsonArray: " + myJsonArray);
return myJsonArray;
}
}
} // onreadystatechange
} // var myJsonData
console.log("myJsonArray: " + myJsonArray); // undefined
Or:
window.onload = myJsonData;
// etc.
Of course myJsonArray is undefined either way. I'm obviously missing something fundamental about how to do this (or is this even a bad idea altogether?). Is there some way to pass a result as a callback when invoking an ajax request on load?
Can someone please enlighten me as to how to proceed from here, a skeletal example perhaps ? (p.s. still focusing on native js, not jQuery)
Many thanks in advance,
svs
There are some things wrong in the code. First the myJsonArray is local to the function myJsonData(). You have to define it globally. Second, the ajax request is async. You have to wait for the result before working with it. Here is a working example based on your code:
var myJsonArray; //Globally defined
window.onload = function() {
var outputData = function() {
console.log(myJsonArray);
}
var myJsonData = function() {
var request = new XMLHttpRequest();
request.open("GET", "someJsonData.json", true);
request.send(null);
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
myJsonArray = JSON.parse(request.responseText);
outputData(); //Ouput when result is received
}
}
} // onreadystatechange
} // var myJsonData
myJsonData(); // *
}
Using Javascript promises could solve your problem here. Promises are native to ES6, although obviously the browser support isn't quite fully there yet. Regardless, here is how you could implement your code with promises:
window.onload = function () {
myJsonData().then(function (result) {
console.log("myJsonArray: " + result);
alert(result.one);
// do something else here
});
}
function myJsonData() {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open("GET", "http://echo.jsontest.com/key/value/one/two", true);
request.setRequestHeader("content-type", "application/json");
request.send(null);
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
var myJsonString = JSON.parse(request.responseText);
//var myJsonArray = myJsonString["Projects"];
resolve(myJsonString);
}
}
} // onreadystatechange
});
} // var myJsonData
Here is a jsfiddle
From my experience and understanding.
When you are dealing with sync function and async function, always it is important to know which functions require data from the async callback function.
If the functions fully depend on the data received, it will a good coding practice to call these function in the loop
if (request.readyState === 4) {
if (request.status === 200) {
var myJsonString = JSON.parse(request.responseText);
// call the function which depend on the data received here.
}
}
I wouldn't recommend setting the async as false as it would just result in page freeze sometimes.
I'm at peak-frustration trying to resolve my mental block re: callbacks. I've read How to return value from an asynchronous callback function? and How to return the response from an Ajax call? (among many other posts), and indeed the latter was helpful with another problem. However what I'm trying to do now is just slightly different and I'm losing my mind trying to adapt it to my code. Maybe my approach is entirely wrong/fundamentally flawed (and not just immature, which I can live with)?
The essence of my problem is that rather than simply returning ajax result to a callback function, I need the resulting json to be available to different functions, corresponding to different events, i.e.:
linkOne.onclick = invoke ajaxReq + getJsonData, then call functionOne with getJsonData result as an argument
linkTwo.onclick = invoke ajaxReq + getJsonData, then call functionTwo with getJsonData result as an argument
linkThree.onclick = invoke ajaxReq + getJsonData, then call functionThree with getJsonData result as an argument
Can't this be done with the link.onclick definition? Why doesn't this work:
linkThree.onclick = functionOne(getJsonData);
Here's my code:
function ajaxReq() {
var request = new XMLHttpRequest();
return request;
}
function getJsonData() {
var request = ajaxReq();
request.open("GET", "/myJSON.json", true);
request.setRequestHeader("content-type", "application/json");
request.send(null);
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
var myJsonString = JSON.parse(request.responseText);
var myJsonArray = myJsonString["An Array in myJSON.json"];
// functionOne(myJsonArray); // callback: what if I need to pass this value to various functions?
return myJsonArray; // ... 'cause this ain't doin' it, and I don't know why
}
}
} // onreadystatechange
} // getJsonData
function functionOne(myJsonArray) {
var myJsonArray = getJsonData(); // why doesn't this work, since, in getJsonData, var request = ajaxReq(); returns an ajax request ?
}
And why, if var request = ajaxReq(); invokes ajaxReq function and returns its result to getJsonData, does var myJsonArray = getJsonData(); in functionOne not do the same?
Any help with this is much appreciated. (p.s. seeking a pure javascript fix, not jQuery.)
svs
As it has been answered in the links you have specified, that we cannot return value from asynchronous call to use it in a synchronous function call. So here is the trick -
Assign all the onclick listeners a common function.
link1.onclick = someCommonfunction;
link2.onclick = someCommonfunction;
link3.onclick = someCommonfunction;
And define the common function like following, which will have json data in the callback, and you can pass that data to any function call.
function someCommonfunction(e) {
/* this is the function which will be finally executed with json data after clicking */
var callback = function(jsonData) {
var myJsonArray = jsonData;
//do some condition check and call functionOne, functionTwo or functionThree
};
getJsonData(callback);
}
I modified getJsonData to call callback with the response data.
function getJsonData(callback) {
var request = ajaxReq();
request.open("GET", "/myJSON.json", true);
request.setRequestHeader("content-type", "application/json");
request.send(null);
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
var myJsonString = JSON.parse(request.responseText);
var myJsonArray = myJsonString["An Array in myJSON.json"];
callback(myJsonArray);
}
}
} // onreadystatechange
} // getJsonData
I have a function which attempts to capture a return value from a calling function in the following manner:
var select = xhrRetrieve(projID);
Here is an example of the xhrRetrieve function:
function xhrRetrieve(projID) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
var obj = $.parseJSON(xhr.responseText);
return obj.select.toString();
}
}
}
var url = "ajax.cgi";
var data = "action=retrieve-opp&proj-id=" + projID;
xhr.open("POST",url);
xhr.setRequestHeader("Content-Type","application/x-www-urlencoded");
xhr.send(data);
}
I am using jQuery in conjunction with straight JavaScript. Whenever I attempt to get the value of obj.select using:
var select = xhrRetrieve(projID);
Select always comes back undefined.
What am I doing wrong?
The function doesn't return anything
The moment you call your function, the (not currently present) return value is being assigned to select. At the same moment, your ajax request is being fired, which takes time to complete; the callback function will not be called until the ajax request has completed (and succeeded).
This should work:
function doStuffWithTheAjaxResponse(select) {
// do stuff
}
function xhrRetrieve(projID) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
var obj = $.parseJSON(xhr.responseText);
doStuffWithTheAjaxResponse(obj.select.toString());
}
}
}
var url = "ajax.cgi";
var data = "action=retrieve-opp&proj-id=" + projID;
xhr.open("POST",url);
xhr.setRequestHeader("Content-Type","application/x-www-urlencoded");
xhr.send(data);
}
Since the request is asynchronous the function will return before your code in onreadestatechange fires. You can switch to synchronous and get the value before the function returns:
function xhrRetrieve(projID) {
var returnVal;
var xhr = new XMLHttpRequest();
var url = "ajax.cgi";
var data = "action=retrieve-opp&proj-id=" + projID;
//3rd param is false to switch to synchronous
xhr.open("POST",url, false);
xhr.setRequestHeader("Content-Type","application/x-www-urlencoded");
xhr.send(data);
if(xhr.readyState == 4) {
if(xhr.status == 200) {
var obj = $.parseJSON(xhr.responseText);
return obj.select.toString();
}
}
}
The function xhrRetrieve doesn't have a return value. What do you expect to happen?
You have two functions there. The inner function returns a value, but not the outer one. The inner function is an event handler so the return value doesn't go anywhere. Your XMLHttpRequest is asynchronous, so you won't get a return value right away. See this post for a more detailed explanation: parameter "true" in xmlHttpRequest .open() method