I have an old function that is no longer functional. According to the console window, synchronous mode is no longer permitted for use. How would I convert this to use asynchronous mode and deliver the data out?
var loadfile = function (filename, filetype) // Reads a file and returns it's contents as a string.
{filetype = filetype || 'text' // Assume text if no filetype is passed in
var reader = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP"); // Provide a fallback for IE
reader.responseType = filetype; // Prepare to read the proper type of data
reader.open("GET", filename, false); // Target our local filename and prepare for synchronous read
reader.send(); // Begin the read
return reader.responseText; // Return the data as expected
};
I know I could force this to operate by removing line 4, but then I get XML Errors and warnings about the depreciated synchronous mode being employed. Warnings that could become 'no longer supported' errors that would block the program run months from now.
Also, I can switch to asynchronous mode by changing line 5's false to true, but then there's no data being passed out as the last line is invoked immediately. I could involve reader.onloadend() to process the data, but a return invoked there just casts the read data into the void when I need it to be passed back to the caller of loadfile().
Ergo, I'm stuck. What am I missing here?
EDIT: Adding a potential async version here and pointing out how it doesn't work.
var loadfile = function (filename, filetype)
{var output = '';
filetype = filetype || 'text';
var reader = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP");
reader.responseType = 'text';
reader.open('GET', filename);
reader.onloadend = function () {output = reader.responseText;}
reader.send();
while (output === '') {}
return output;
};
Naturally this has several issues. First, the scope of reader.onloadend() makes it's copy of output separate from the one in loadfile(). So the retrieved data just disappears. We could return it instead of assigning it to a local variable, but onloadend fires itself (as events do) and the returned data is sent into the void. Unrecoverable. Second, even if we could change loadfile()'s version of output from within the onloadend() function as per passing by reference in C++, the while loop that waits for the output variable to change would (because JavaScript is a single process thread) lock up the system, running an infinite loop and not allowing any changes to occur.
Ergo, still stuck. Yes, we could output to window.name or console.log or document.write, but none of these options allows loadfile() to return the data acquired from XHMLHttpRequest/filename.
At this point I'm stuck with the depreciated synchronous xhr, and XML Parsing Errors as I am retrieving raw text, not XML.
EDIT SECUNDUS: I finessed the script to default to synchronous mode, and managed to silence the errors (but not the initial warning about using a depreciated method...thankfully that doesn't spam warnings on every usage). Needless to say using async/true on this function will not get you anywhere, but async/false does work....for now. Sharing in case anyone else needs this particular functionality.
var loadfile = function (filename, async)
{if ("undefined" === typeof(async)) {async = false;}
var reader = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP");
reader.open('GET', filename, async);
reader.onloadend = function () {return reader.responseText;}; // Lost to /dev/null while the program executes without this data. Unacceptable!
reader.overrideMimeType('text/plain');
try
{reader.send();}
catch (e)
{return null;}
if (!async) {return reader.responseText;}
//TODO: Find some way to delay execution without generating an infinite loop (good luck - javascript is not multithreaded so we cannot use a while loop for this)
//TODO: Extract reader.responseText from within reader.onloadend() (probably by invoking a global temp object to shift the data out of onloadend()).
};
Promises can help you make your asynchronous code more manageable, but it is not required.
In your case, you could simply use event listeners.
var xhr = new XMLHttpRequest() || new ActiveXObject("Microsoft.XMLHTTP");
xhr.responseType = 'text';
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1');
xhr.onload = function(){
var res = JSON.parse(this.responseText);
// do something with the response
document.write('<h1>' + res.title + '</h1>');
document.write('<p>' + res.body + '</p>');
}
xhr.send();
In other words, you need to refactor your code to remove the need to call for a function that returns something (or, simply, your function should call the rest of the code that requires this xhr through an event handler).
You can read more about the XHR requests and their possible results, events, etc. here.
in order to birng your function up to date, you should encapsulate it into a Promise. Here is an older post explaining:
How to promisify native XHR
With callback
function makeRequest (method, url, done) {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onload = function () {
done(null, xhr.response);
};
xhr.onerror = function () {
done(xhr.response);
};
xhr.send();
}
// And we'd call it as such:
makeRequest('GET', 'http://example.com', function (err, datums) {
if (err) { throw err; }
console.log(datums);
});
Related
I'm writing some code where there will be one function (doAjax) to handle all the requests for different functionalities. This is working fine when used in the normal sense (clicking buttons, etc), but I'm trying to call a few things when the page is loaded to initialise the state of the application.
Here's the Ajax function:
function doAjax(type, action, data = null){
return new Promise(function(res,rej){
xhr = new XMLHttpRequest();
xhr.open(type, '/inc/functions.php?action='+action, true);
xhr.timeout = 20000;
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
if (this.status === 200) {
res(xhr);
} else {
console.log("some xhr error occured");
rej("some xhr error happened");
}
};
xhr.send(data);
});
}
And here are a couple of examples of functions that send requests to this:
/* get file structure of files */
function buildTree(){
doAjax('GET', 'get_files').then(r => {
var res = JSON.parse(r.response);
console.log(res);
/*
the json is processed here and stuff
is displayed on the front end
*/
}
}
/* get user data from database */
function populateShare(){
doAjax('GET', 'social_populate').then(r => {
var res = JSON.parse(r.response);
console.log(res);
/*
again, the json received from the database is
processed and output to the front end
*/
}
}
I should also mention these functions are also bound to click listeners, so are used in the application later as well as onload!
Now, the problem is when I try and execute both of these on page load as a kind of initialise function (to set up the application ready for use). If I run the below:
function init(){
buildTree();
populateShare();
}
init(); //called later
Both console.logs output the same result - the return from social_populate (called from populateShare()).
So my question ultimately is whether there are any ways to queue function calls in the init() function, waiting for each to finish before moving onto the next? There may be more functions that need to be called on init(), some also involving Ajax requests.
I have tried the following, found in another thread - but unfortunately returns the same result:
async function initialise(){
try {
const p1 = buildTree();
const p2 = populateShare();
await Promise.all([p1, p2]);
} catch (e) {
console.log(e);
}
}
I know I could load the entire lot from the back end and return in one huge JSON, but I'm more curious to whether the above can be achieved! I feel like I've entered some kind of synchronous request death loop, or I'm missing something blatantly obvious here!
Also, no jQuery please!
Thanks!
You appeared to have accidentally used global variable when you declare xhr which was overwritten at each call.
Try let xhr = new XMLHttpRequest();
I'm currently writing a search function using JavaScript.
However, when I attempt to test my creation, I find that it stops about halfway through for no discernible reason.
Below is my code:
document.getElementById("test").innerHTML = "";
var Connect = new XMLHttpRequest();
Connect.open("GET", "xmlTest.xml", false);
document.getElementById("test").innerHTML = "1";
Connect.send(null);
document.getElementById("test").innerHTML = "2";
var docX = Connect.responseXML;
var linjer = docX.getElementsByTagName("linjer");
The first line is there to clear a potential error message from earlier in the code. Then I attempt to open up an XML file, as I need to read from it.
As you can see, I've entered two debug statements there; they will print 1 or 2 depending on how far I get in the code.
Using this, I've found that it stops exactly on the Connect.send(null); statement (as 1 gets printed, but 2 never does), but I can't figure out why. Google says that it might be that chrome can't access local files, but when I found a way to allow Chrome to do this, it still did not work.
What am I doing wrong?
This might be a synchronous issue that requires a response that your code simply is not getting.
Try using an async call instead:
Connect.open("GET", "xmlTest.xml", true);
Also make sure to setup proper callbacks since you'll be using async here now instead of synchronous code, like so:
// Global variable scope
var docX;
var linjer;
// Define your get function
getDoc = function(url, cbFunc) {
var Connect = new XMLHttpRequest();
// Perform actions after request is sent
// You'll insert your callback here
Connect.onreadystatechange = function() {
// 4 means request finished and response is ready
if ( Connect.readyState == 4 ) {
// Here is where you do the callback
cbFunc(Connect.responseXML);
}
};
// 'true' param means async, it is also the default
Connect.open('GET', url, true);
Connect.send();
}
// Define your callback function
callbackFunction = function(responseXML) {
// XML file can now be stored in the global variable
window.docX = responseXML;
window.linjer = window.docX.getElementsByTagName("linjer");
}
// And here is the call you make to do this
getDoc("xmlTest.xml", callbackFunction);
For better understanding of all of this, do some research on scope, closures, callbacks, and async.
In this discussion and in chat I understood that a callback is the only way to go!
"
Get from the server a link with ajax, write the link in a variable, open an xml with this link, doing some stuff with the xml: is callback the only way?
"
I'm trying to understand what a callback is. I read some blog, but I still have problems.
What I have now in JS is
1) a function to open an xml.
2) function to request the link of the xml in first function
Can someone provide an example in PLAIN JAVASCRIPT of how to nest these two functions?
The server generate the link of the xml because I'm making a multi user web site and every user has it's own xml. So I need to ask the server what is the link of the xml and then open it. Is there an easy way to achieve this? I need plain javascript no jquery.
Thanks!
In general, a "callback" is a function which will be executed at a later time when an asynchronous process is completed.
So you might start by defining the function that should happen when the data is retrieved from the server (the "second" function, intuitively, but you should define it first because it's the business functionality you're looking to achieve and not just an implementation concern). Something as simple as:
var doSomethingWithTheData = function () {
// do, well, something with the data
};
This assumes that you have the data, which you don't yet. But the AJAX call will get that data. You can now use this function as your callback for the AJAX call. Taking the AJAX example from MDN, you might have this:
var httpRequest;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE 8 and older
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
httpRequest.onreadystatechange = doSomethingWithTheData;
// perform the AJAX request
The httpRequest object will contain the response from the server after the AJAX call is executed and completed. (Remember that this happens asynchronously, so it won't contain the response on the immediate next line of code. It will at some later time which you don't control. Hence the need for the callback.)
I recommend walking through that full MDN article to get all the details, particularly on handling errors and such. But the data you're looking for (assuming nothing went wrong) would be in httpRequest.ResponseText. So, also assuming your variables are scoped to allow this (you can modify that as needed):
var doSomethingWithTheData = function () {
var data = httpRequest.ResponseText;
// do, well, something with the data
};
Excuse me, #David
var httpRequest;
if (window.XMLHttpRequest) { // Mozilla, Safari,
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE 8 and older
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
var url = "http://myserver.mydomain/getMyUsersXMLUrl?user=pete";
httpRequest.open("GET", url, true); // next ajax to retrieve XML - File
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState == 4) { // response received
var response = httpRequest.responseText; // this should contain you url
httpRequest.open("GET", response, true); // next ajax to retrieve XML - File
// and the same as for the first request
}
}
If You just need to download a xml you dont need a callback . Just take a look at jquery and ajax. Retrieving a callback from a server is not possible . JSONP does deal with callbacks that are called by the server (actually they arent ) Butter you wont need it. I think you are messaging the xhttprequestobject
A callback function is simply a piece of executable code passed as a parameter to another piece of code. For example:
function first (arr, predicate) {
// no predicate supplied, return first element
if (!predicate) return arr[0];
for (var i = 0; i < arr.length; i++) {
// return first element satisfying predicate
if (predicate(arr[i])) return arr[i];
}
// no element satisfying predicate, return null
return null;
}
// second parameter is an anonymous function
// will alert 4, as it's the first element which will return true
alert(first([1, 2, 3, 4, 5, 6], function(n) { return n > 3; }));
Callbacks are useful for asynchronous tasks, or for library functions which need extra customization at runtime.
I am using a second party file downloader which returns a progress event. I can capture the event and call a program on the server to perform an update (for security purposes so I can tell the most recent activity).
I get about 30 events per second all at percent downloaded 1%, then 30 more at 2%, then 30 more at 3%, etc. I would like to limit my http calls to only once per percentage change, 1%, 2%, 3%, etc. I would put a hidden field on the page and compare that and update it, but I cannot refresh the page since the download is in progress.
Is there a way to use some type of client side storage within javascript or jquery for this?
In other words, I need to be able to tell when the PercentCurrent value changes from 1 to 2, etc.
My javascript function looks like this:
function onProgress(PercentTotal, PercentCurrent, Index){
var xmlhttp;
//The handler will update the file progress
if (typeof XMLHttpRequest != 'undefined') {
xmlhttp = new XMLHttpRequest();
}
if (!xmlhttp) {
throw "Browser doesn't support XMLHttpRequest.";
}
var data = "";
xmlhttp.open("POST", "UpdateProgress.aspx?PercentCurrent=" + PercentCurrent, true);
//Send the proper header information along with the request
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
//xmlhttp.setRequestHeader("Content-length", data.length);
xmlhttp.setRequestHeader("Connection", "close");
xmlhttp.send(data);
}
Thank you,
Jim
JavaScript does indeed have variables, you just need to store one in a scope that's accessible to your onProgress code. You may just be able to use a var in the same place onProgress is declared, but a simple and JavaScripty way to make that variable "private" is to use a closure:
var onProgress = (function(){
var lastSend = 0;
return function(PercentTotal, PercentCurrent, Index){
if (Math.floor(PercentCurrent) > lastSend) {
lastSend = PercentCurrent;
var xmlhttp…
}
}
})();
This'll look a little confusing if you haven't worked with JavaScript much. Here's what's going on:
I create a variable called onProgress
I create and immediately run an anonymous (unnamed) function, like this: (function(){ … })()
This function defines a local variable, lastSend, and returns the real onProgress function.
Whenever a function is called in JavaScript, it has access to the scope in which it was created. So, whenever onProgress() is called, it'll have access to the lastSend variable, and can check that progress is has moved past the next whole percent.
Of course, this is a bit ugly, and it can only be used once on a page (since there's only one closure with one lastSend variable. Instead of assigning it to a name, you might pass it directly into the function which calls it, anonymously (see below). Then, a new copy of the function, with a new closure, gets created when you hit downloadFile.
Your original question is tagged jquery. If you are indeed using jQuery on the page, you can simplify the posting of data significantly (down to one line) and make it more compatible, with jQuery.post:
$.post("UpdateProgress.aspx", { PercentCurrent: PercentCurrent });
(This would replace all the XMLHTTPRequest-related code in onProgress.)
So, using a closure and jQuery.post might look like this:
// Not sure what your second-party file downloader looks like
fileDownloader.downloadFile((function(){
var lastSend = 0;
return function(PercentTotal, PercentCurrent, Index){
if (Math.floor(PercentCurrent) > lastSend) {
lastSend = PercentCurrent;
$.post("UpdateProgress.aspx", { PercentCurrent: PercentCurrent });
}
}
})());
Have a look at jQuery's .data(). It allows you to store data and attach it to a particular DOM element like so:
$('body').data('foo', 52);
$('body').data('foo'); // 52
I am not sure to understand your problem. Is the page continously reloaded? If it is not all that you need to do is:
var lastPercent = null; // you need to initialize this when it all starts again.
function onProgress(PercentTotal, PercentCurrent, Index){
var xmlhttp;
if (lastPercent == PercentCurrent)
return; //Does nothing if no change occurred.
lastPercent = PercentCurrent;
//The handler will update the file progress
if (typeof XMLHttpRequest != 'undefined') {
xmlhttp = new XMLHttpRequest();
}
if (!xmlhttp) {
throw "Browser doesn't support XMLHttpRequest.";
}
var data = "";
xmlhttp.open("POST", "UpdateProgress.aspx?PercentCurrent=" + PercentCurrent, true);
//Send the proper header information along with the request
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
//xmlhttp.setRequestHeader("Content-length", data.length);
xmlhttp.setRequestHeader("Connection", "close");
xmlhttp.send(data);
}
I'm using AHAH (as outlined here http://microformats.org/wiki/rest/ahah) to make two calls to populate HTML on a page. The calls happen after the document is ready and are fired off one after another. The result, every time, is the first call gets overwritten with the last calls response. So I'll have two of the same chunks of HTML on the page instead of two unique pieces of code. Sometimes the first call doesn't even get to evaluate it's call back and thus remains empty.
Any ideas?
If you're using the exact code on that page, it's not surprising, as the example there uses a single global variable to store the XMLHttpRequest being made. So there's no way it can work for more than one simultaneous request: calling the function a second time overwrites the req with a new one, causing the req read by ahahDone to be the wrong request.
If you want to allow this you'll have to make req a local variable (by declaring it var in function ahah()), and pass it with the target to the ahahDone() function. Or just do it inline:
function Element_loadHTML(element, url) {
var req= null;
if (window.XMLHttpRequest) {
req= new XMLHttpRequest();
} else if (window.ActiveXObject) {
try {
req= new ActiveXObject('MSXML2.XMLHttpRequest');
} catch() {}
}
if (!req) {
element.innerHTML= 'Browser does not support XMLHttpRequest';
return;
}
element.innerHTML= 'Loading...';
req.onreadystatechange= function() {
if (req.readyState===4)
element.innerHTML= req.status===200? req.responseText : 'Error '+req.status;
};
req.open('GET', url);
req.send(null);
}
Element_loadHTML(document.getElementById('appdata'), 'appdata.part.html');
Element_loadHTML(document.getElementById('foo'), 'bar.part.html');
The stuff with the browser sniffing and trying to execute script tags is hopeless and broken; don't use it. It's not good practice to be loading <script> element content into the page.