I'm trying something very simple for my first Firefox Add-On, the important part is:
Step 1) Call an external API to retrieve some data.
Step 2) Call that API again with the data retrieved the first time to get some more.
Now, I first implemented it using XMLHttpRequest in synchronous mode, since I thought the need to wait for Step 2 forced me to do it that way. Two calls to the function that dealt with the API call, used XMLHttpRequest and parsed the response. Fine.
Then I came accross various docs in the Mozilla Development Network which encourage you to use XMLHttpRequest in asynchronous mode and so I tried.
Basing my implementation on multiple XMLHttpRequests and others I came up with the code below.
My question is: Is this the proper way to do it? Should I go back to using synchronous mode? It works like this, but it just doesn't strike me as the correct AJAX pattern you would use...
// first call
var username = foo;
var password = bar;
var startOffset = 0; // initial value
var url = encodeURIComponent('https://theapiurl.com/query=' + startOffset);
doRequest();
function doRequest() {
makeRequest(url, username, password);
}
function makeRequest(url, username, password) {
var http_request = new XMLHttpRequest();
if (http_request.overrideMimeType) {
http_request.overrideMimeType('text/xml');
}
if (!http_request) {
alert('Cannot create XMLHTTP instance');
return false;
}
http_request.onreadystatechange = function() {
alertContents(http_request);
};
http_request.open('GET', url, true, username, password);
http_request.send(null);
}
function alertContents(http_request) {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
if (startOffset == 0) {
startOffset = 45; // this value would be extracted from 'http_request'
url = encodeURIComponent('https://theapiurl.com/query=' + startOffset);
// second call, parameter startOffset has changed
doRequest();
} else {
}
} else {
alert('There was a problem with the request.');
}
http_request.onreadystatechange = function fnNull(){};
}
}
You should always avoid doing synchronous network requests as it will block the GUI from functioning until you get a response. Just because the network may be fast for you, you should not assume it will be fast for all of your users.
Related
Say I'm interested in checking the params that were sent with the XMLHttpRequest.
For instance, if I sent a POST petition with param 'option=1' can I retrieve that from the response?
I checked for the methods and properties but haven't seen a way to get it.
Fire a XMLHTTPRequest and examine the response object in your browser's JS console (F12 for Chrome/Firefox).
I believe the data is not there, at least I once changed the XMLHttpRequest open() method for a project (of course, I might have been just too stupid to find it). That way, my default error handler knows the original URL when printing errors to the user/sending errors to the error reporting backend.
Rough code snippet, pulled from a projects init-code:
/**
* Check XMLHttpRequest availability
*/
var ajax = null;
var proto = null;
if (window.XMLHttpRequest) {
ajax = new XMLHttpRequest ();
proto = XMLHttpRequest.prototype;
} else if (window.ActiveXObject) {
try {
ajax = new ActiveXObject("Msxml2.XMLHTTP.6.0");
proto = ActiveXObject("Msxml2.XMLHTTP.6.0").prototype;
} catch (e) { }
}
if (ajax == null) {
alert ("Can not create AJAX object. You need a more recent browser!");
return;
}
/**
* Update ajax prototype to store the URL (for better error handling)
*/
try {
var origOpen = proto.open;
proto.open = function (method, url) {
this._url = url;
return origOpen.apply (this, arguments);
}
} catch (e) {
console.log ("Can not patch XMLHttpRequest to store URL. Console output will omit them...");
}
You would need to adapt this for POST data passed to the send() function instead. Be aware, that the method is probably bad style, and my JS style might be even worse!
Better: But you could always pass the POST data directly to the callback function, without storing it in the XMLHTTPRequest object:
var postData = "SomeStuff-Foobar123";
var ajax = new XMLHttpRequest (); //add magic for other browsers here
ajax.open ("POST", "ajax.php", true);
ajax.onreadystatechange = function () {
if (this.readyState != 4 || this.status != 200) {
console.log ("Not ready, yet...");
return 0;
}
//response is in this.responseText
//but you can still access the parent objects!
console.log ("Done with Code 200. POSTed data: " + postData);
}
ajax.send (postData);
As Bergi said it's not possible to retrieve the parameters that were sent with the request on the response. So I'm closing the question.
Thanks to everyone who helped!
I am updating text in text area with javascript every 2 seconds, however sometimes happen that entire page freezes and you have to close the tab (other tabs in browser are working normally, this happens to all people visiting the page).
This is how my code looks like:
function ajaxSyncRequest(reqURL) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", reqURL, false);
xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xmlhttp.send('server=" + server + "');
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200){
document.getElementById(\"1\").innerHTML = xmlhttp.responseText;
if (document.getElementById('check').checked) { document.getElementById(\"1\").scrollTop = document.getElementById(\"1\").scrollHeight; }
} else {
document.getElementById(\"1\").innerHTML = "Could not connect to remote server!";\n
}
}
}
And this is the 2 seconds timer:
function timer() {
ajaxSyncRequest("ConsoleGenerator");
window.setTimeout("timer()", 2000);
}
I am getting the text with POST method to Java Servlet. It works sometimes for hours and then it freezes and browser says "Page is not reposnding..." or sometimes it works just a few minutes and then it freezes...
Can anybody help please ?
(Assuming we fix the basic syntax errors in the code.) You're happily firing off a subsequent requests without waiting for previous ones to complete. If the ajax call ever takes more than two seconds, you'll have overlapping calls. That isn't a problem in and of itself unless your backend is serializing calls or similar, but it does set up a chaotic situation.
You're also making synchronous requests by specifying false as the third argument to the POST call. There's no need to make the request synchronous, and doing so (particularly every two seconds?!) will indeed tend to lock up the UI of the browser.
I would recommend waiting for the previous request to complete before scheduling the next one, and making the requests asynchronous so the browser UI isn't locked:
// Accept callback --------------v
function ajaxSyncRequest(reqURL, callback) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", reqURL, true);
// async, not sync ----------^
xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xmlhttp.send('server=" + server + "');
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200) {
document.getElementById("1").innerHTML = xmlhttp.responseText;
if (document.getElementById('check').checked) {
document.getElementById("1").scrollTop = document.getElementById("1").scrollHeight;
}
} else {
document.getElementById("1").innerHTML = "Could not connect to remote server!\n";
}
callback(); // <== Call it
}
}
function timer() {
ajaxSyncRequest("ConsoleGenerator", function() { // Pass in a callback
setTimeout(timer, 2000);
});
}
That will wait for the ajax to complete and then schedule another update two seconds later. If you want to include the time the ajax call took in the two seconds instead, we can do some basic math:
function timer() {
var started = Date.now();
ajaxSyncRequest("ConsoleGenerator", function() { // Pass in a callback
setTimeout(timer, Max.max(0, 2000 - (Date.now() - started));
});
}
Side note: No need for the window. prefix on setTimeout (though it's harmless provided nothing's shadowed the global window), and rather than passing a string to it, just pass a function reference.
I've searched SO for similar issues (e.x. Chrome does not redraw <div> after it is hidden and Force DOM redraw/refresh on Chrome/Mac ) but none of questions gave me the solution to my problem. I am writing modem configuration panel, the webpage with ,,tabs''. On every tab there are some settings-just like configuration panel of any router.
Saving configuration (done when user clicks on Save button) takes few seconds (my embedded platform is not a speed king), so I decided to put special PLEASE WAIT window (div to be precise) which is usually hidden, but is shown when needed to calm user down :-).
Everything works fine on Firefox: after clicking save, the PLEASE WAIT div shows and then the configuration is saved using POST method. However, on Chrome 26 and Chromium 25 the div does not show until the configuration is saved. As you can see in SaveConfiguration function after executing PHP script that saves configuration the alert is shown-this is where the PLEASE WAIT div shows up on Chrome. It looks like Chrome is not redrawing page but immediately starts launching POST script. Has anyone had similar issues and now how to fix this problem?
Below are fragments of my code, I have only supplied functions that might give a clue what I'm doing. I can post more code if that helps.
function showLoadingScreen(yes)
{
if(yes)
{
document.getElementById("loadingtext").innerHTML="Please wait...";
document.getElementById("loading_overlay").style.display="block";
document.getElementById("loading_window").style.display="block";
}
else
{
document.getElementById("loading_overlay").style.display="none";
document.getElementById("loading_window").style.display="none";
}
}
function postDataSync(url, params)
{
var XMLHttpRequestObject = false;
if (window.XMLHttpRequest)
{
XMLHttpRequestObject = new XMLHttpRequest();
} else
if (window.ActiveXObject)
{
XMLHttpRequestObject = new
ActiveXObject("Microsoft.XMLHttp");
}
if(XMLHttpRequestObject)
{
XMLHttpRequestObject.open("POST", url, false);
XMLHttpRequestObject.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
XMLHttpRequestObject.send(params);
{
if (XMLHttpRequestObject.readyState == 4 &&
XMLHttpRequestObject.status == 200)
{
var result = XMLHttpRequestObject.responseText;
delete XMLHttpRequestObject;
XMLHttpRequestObject = null;
return result;
}
}
}
return '';
}
function SaveConfiguration()
{
var errors=checkForm();
if(errors!="")
{
printError("Can't save configuration because there are errors in current tab:<br><br>"+errors);
return;
}
showLoadingScreen(true);
saveTab();
var retval=postDataSync('actions/saveconf3.php','');
alert("Settings saved. The modem is now being reconfigured.");
document.location = "http://" + retval;
}
You are using ajax synchronously rather than asynchronously meaning javascript execution halts during the request. To fix make the following change:
XMLHttpRequestObject.open("POST", url, true);
You need to use a callback for the behaviour after the request is complete. Something like this:
function postDataSync(url, params, success)
{
var XMLHttpRequestObject = false;
if (window.XMLHttpRequest)
{
XMLHttpRequestObject = new XMLHttpRequest();
} else
if (window.ActiveXObject)
{
XMLHttpRequestObject = new
ActiveXObject("Microsoft.XMLHttp");
}
if(XMLHttpRequestObject)
{
XMLHttpRequestObject.open("POST", url, true);
XMLHttpRequestObject.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
XMLHttpRequestObject.send(params);
XMLHttpRequestObject.onreadystatechange = function() {
if (XMLHttpRequestObject.readyState == 4 &&
XMLHttpRequestObject.status == 200)
{
var result = XMLHttpRequestObject.responseText;
delete XMLHttpRequestObject;
XMLHttpRequestObject = null;
if (typeof success === 'function') success(result);
}
}
}
return '';
}
function SaveConfiguration()
{
var errors=checkForm();
if(errors!="")
{
printError("Can't save configuration because there are errors in current tab:<br><br>"+errors);
return;
}
showLoadingScreen(true);
saveTab();
postDataSync('actions/saveconf3.php','', saveComplete);
}
function saveComplete(result) {
showLoadingScreen(false);
alert("Settings saved. The modem is now being reconfigured.");
document.location = "http://" + result;
}
If you have heavy synchronous code (in practice, operations on hundreds or thousands of objects that are already in memory, or calculating pi to a gazillion digits) you can use setTimeout to give the browser time to catch up with any rendering tasks. You'd either need to call setTimeout for each task, or if you have a long-running task, split it up in batches first. This requires quite a bit of refactoring though, since every task needs to be represented as a function that can be passed to setTimeout.
I wouldn't use XMLHTTPRequest synchronously ever.
If setTimeout(fn, 0) does not trigger the "incremental" rendering, try a higher value, until it works. I think I needed to use a value of 100ms between jobs in some cases, for some browsers (I don't recall which).
You may need to yield to the browser even quicker if you want to achieve 60fps, or 30fps. Then you need to stay under 16ms or 33ms for each task. That gets very tight on slow hardware, such as (older types of) smartphones. Then, instead of setTimeout, you can best use requestAnimationFrame, if available.
I am trying to get the html of a website using this code:
function catchData(req) {
console.debug("i got a reply!");
var returnXML = req.responseXML;
console.debug(returnXML);
if (!returnXML)
{
console.debug("html is bad");
return;
}
if (speed != currentSpeed)
moveToNewSpeed(speed);
currentSpeed = speed;
var error = returnXML.getElementsByTagName('message')[0].firstChild;
if (error) {
document.getElementById('errorMessage').innerHTML = error.nodeValue;
document.getElementById('errorMessage').style.visibility = 'visible';
}
else
document.getElementById('errorMessage').style.visibility = 'hidden';
}
function sendRequest(url,callback,postData) {
console.debug(url);
console.debug(postData);
var req = createXMLHTTPObject();
if (!req) return;
var method = (postData) ? "POST" : "GET";
console.debug(method);
req.open(method,url,true);
console.debug("request Opened");
req.setRequestHeader('User-Agent','XMLHTTP/1.0');
req.setRequestHeader('User-Agent','XMLHTTP/1.0');
if (postData)
{
req.setRequestHeader('Content-type','application/x-www-form-urlencoded');
console.debug("set post data");
}
req.onreadystatechange = function () {
if (req.readyState != 4)
{
console.debug("bad ready state");
return;
}
console.debug(req);
console.debug("responseText:");
console.debug(req.responseText);
callback(req);
console.debug("callback finished");
}
if (req.readyState == 4) return;
req.send(postData);
}
var XMLHttpFactories = [
function () {return new XMLHttpRequest()},
function () {return new ActiveXObject("Msxml2.XMLHTTP")},
function () {return new ActiveXObject("Msxml3.XMLHTTP")},
function () {return new ActiveXObject("Microsoft.XMLHTTP")}
];
function createXMLHTTPObject() {
var xmlhttp = false;
for (var i=0;i<XMLHttpFactories.length;i++) {
try {
xmlhttp = XMLHttpFactories[i]();
}
catch (e) {
continue;
}
break;
}
return xmlhttp;
}
When I do a wireshark grab I see the server returning the html, but req.responseText is just an empty string. Anyone know whats up?
I guess you're trying to get the HTML of a page that's on a different domain than your JavaScript. This is a cross-domain request, which isn't allowed in Javascript. This is usually seen as empty responses in your script.
The JSONP standard describes a mechanism to retrieve JSON from a different domain, but this needs to be implemented on the other site and doesn't work with HTML.
The Yahoo! Query Language (YQL) can act as a proxy. The Yahoo! server will fetch the HTML and create a JSONP response, which your script will receive. This may help you to accomplish your goal. The YQL has a lot of cool features for retrieving content from other sites, I recommend you read through the documentation to see if there's anything else you can use.
from where is the javascript being executed? Do you have a same-origin policy violation?
I ask because I have seen wierdness in these situations, where I was violating the policy but the request was still going out; just the response was empty...it doesn't make any sense that the browser would send the request, but they all handle it differently it appears...
Is there a reason why you're writing this code yourself instead of using a library such as jQuery? You'll find it much easier and they've already figured out all the associated quirks with browser interoperability, etc.
I am using the following script to monitor whether I can connect to a web site in a regular interval (10 seconds in my sample code). I met with two issues, any ideas how to solve them?
If a web site is very slow and no response within 10 seconds (making PingWebSite not return), I find 2 second call to PingWebSite will be executed because of 10 second interval arrives. My purpose is I want only one call to PingWebSite is under execution, and if 10 seconds interval arrives and previous PingWebSite is executing, I want to prevent current PingWebSite from execution. Any ideas how to solve this?
I find a strange issue, when I connect to a very slow web site, and code path executes to "alert("connecting");", then I expect exception to be thrown for timeout, but in my debug, no exception is thrown. Any ideas how to catch timeout exception?
Here is my code,
var index = 0;
function setup() {
window.setInterval(PingWebSite, (10 * 1000));
}
function PingWebSite() {
var http_request = new XMLHttpRequest();
try {
http_request.open("GET", "http://www.google.com", true);
http_request.onreadystatechange = function() {
if (http_request.readyState == 4) {
if (http_request.status == 200) {
MonitorInformation.innerText = "http://www.google.com" + " Connection ok";
alert("ok");
}
else {
alert("fail");
}
http_request = null;
} // if if (http_request.readyState == 4)
else {
// if execute here, no exception will be thrown
alert("connecting");
}
} // end of function
http_request.send(null);
} // try
catch (e) {
alert("service is not available");
}
}
EDIT 1: I have followed advice here to modify my code. Here is the below version. The new issue is index value (e.g. 0) will be prompted in alert message box before ok/fail alert message box. I think index value (e.g. 0) should be prompted in alert message box after ok/fail alert message box. Any ideas why?
var index = 0;
var http_request;
var xhrTimeout;
var chkConn;
function setup() {
chkConn = window.setInterval(PingWebSite, (10 * 1000));
}
function WebMonitorTimeout() {
http_request.abort();
alert("timeout");
index = index + 1;
}
function PingWebSite() {
http_request = new XMLHttpRequest();
http_request.open("GET", "http://www.google.com", true);
http_request.onreadystatechange = function()
{
if (http_request.readyState == 4) {
if (chkConn) { clearInterval(chkConn); }
if (http_request.status == 200) {
alert("ok");
index = index + 1;
if (xhrTimeout) { clearTimeout(xhrTimeout); }
}
else {
alert("fail");
index = index + 1;
if (xhrTimeout) { clearTimeout(xhrTimeout); }
}
http_request = null;
} //if (http_request.readyState == 4)
} // end of event function
http_request.send(null);
xhrTimeout = setTimeout("WebMonitorTimeout();", 30000);
alert(index);
chkConn = window.setInterval(PingWebSite, (30 * 1000));
}
thanks in advance,
George
Duplicate of javascript connect to web site code not working
You can't do Cross Site XHR requests because of browser security
For your first problem, don't use setInterval – use setTimeout in the callback for your request:
http_request.onreadystatechange = function () {
if (http_request.readyState == 4) {
// ...
setTimeout(PingWebSite, 10000);
}
};
Don't forget to call your function once after it has been defined to start it off (after that setTimeout will be called every time after a request has finished.)
Note that in some cases you might not reach readyState 4. I haven't really looked into how other libraries handle those cases, but look at the source code of jQuery, for example, for inspiration.
<SCRIPT language=javascript>
// Needed for IE6 and older to replicate the standard XMLHttpRequest object
if (window.ActiveXObject && !window.XMLHttpRequest){window.XMLHttpRequest =
function(){progIds=new Array("Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.5.0",
"Msxml2.XMLHTTP.4.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP",
"Microsoft.XMLHTTP");for(i in progIds){try{return new
ActiveXObject(progIds[i]);}catch(ex){alert(progIds[i]);}}return null;};}
// Standard asynchonous AJAX code
var xhr = new XMLHttpRequest();
// You would normally trade out the location.href with an actual .ashx
// page. It's like this here only for testing, thereby requesting this
// same page back from the server.
xhr.open("POST",location.href,true);
// The function that will be called asynchronously when the server sends
// back its response
xhr.onreadystatechange=function(){
// If you're using the file system instead of a web server then xhr.status
// will come back as 0, not 200. And of course if the page isn't found
// then a web server will send back a status of 404. xhr.readyState is 4
// when the page is done.
if (xhr.readyState == 4 && xhr.status == 200) {
clearTimeout(xhrTimeout); // Looks like we didn't time out!
// Use xhr.responseText to parse the server's response
alert(xhr.responseText);
}
}
// Now that we're ready to handle the response, we can make the request
xhr.send("My excellent post info");
// Timeout to abort in 5 seconds
var xhrTimeout=setTimeout("ajaxTimeout();",5000);
function ajaxTimeout(){
xhr.abort();
alert("Well dang, the AJAX request timed out. Did you lose network "+
"connectivity for some reason?");
// Note that at this point you could try to send a notification to the
// server that things failed, using the same xhr object.
}
</SCRIPT>