I need to do a sequential XMLHttpRequest requests (FIFO) to not to call the server with many requests a same time, I wrote this function that do the XMLHttpRequest sequentially:
var queue = [];
var xmlHttpCurrentlyOccuped = false;
function loadUserDetails() {
var url = "https://someurl.com";
doWebRequest("GET", url, "", parseUserDetails);
}
function parseUserDetails(dataFromServer){
Console.log("data received from server: "+JSON.stringify(dataFromServer));
}
function doWebRequest(method, url, params, callback) {
var parametres = new Object();
parametres.myMethod = method;
parametres.myUrl = url;
parametres.myParams = params;
parametres.myCallback = callback;
queue.push(parametres);
while (queue.length>0 && !xmlHttpCurrentlyOccuped){
var doc = new XMLHttpRequest();
doc.onreadystatechange = function() {
var status;
if (doc.readyState == XMLHttpRequest.LOADING || doc.readyState == XMLHttpRequest.OPENED){
xmlHttpCurrentlyOccuped = true;
}
if (doc.readyState == XMLHttpRequest.DONE && doc.status == 200) {
xmlHttpCurrentlyOccuped = false;
var data;
var contentType = doc.getResponseHeader("Content-Type");
data = doc.responseText;
queue[0].myCallback(data);
queue.shift();
}
else if (doc.readyState == XMLHttpRequest.DONE) {
xmlHttpCurrentlyOccuped = false;
status = doc.status;
if(status!=200) {
parseTheError(url);
}
queue.shift();
}
}
doc.open(queue[0].myMethod, queue[0].myUrl);
doc.send();
}
}
My problem is, after the XMLHttpRequest is done well, the callback function is not working in this line of my code queue[0].myCallback(data);I have this error: "queue[0].callback(data): undefined".
Any idea to resolve this issue?
Update:
I resolved the issue, this is my working code maybe it can help someone:
var queue = [];
function doWebRequest(method, url, params, callback) {
var parametres = new Object();
parametres.myMethod = method;
parametres.myUrl = url;
parametres.myParams = params;
parametres.myCallback = callback;
if (queue.length>0) {if (queue[queue.length-1].url != parametres.url) queue.push(parametres);}
else queue.push(parametres);
var doc = new XMLHttpRequest();
function processNextInQueue() {
if (queue.length>0){
var current = queue.shift();
doc.onreadystatechange = function() {
if (doc.readyState == XMLHttpRequest.DONE){
if(doc.status == 200) {
if(typeof current.myCallback == 'function'){
current.myCallback(doc.responseText)
} else {
console.log('Passed callback is not a function');
}
processNextInQueue();
}
else if(doc.status!=200) {
parseTheErrors(current.myUrl);
}
}
}
doc.open(current.myMethod, current.myUrl);
doc.send();
}
}
processNextInQueue();
}
Thank you guys for your help ;)
You can't poll in javascript with a while loop like this and expect proper performance. Javascript is single threaded so when you poll like this, you don't allow any cycles for other things to happen. You need to learn how to write asynchronous code where you start the first ajax call and then return. When that first one completes, you then start the second one and so on.
Here's a way to do this:
queue.push(parametres);
function processNextInQueue() {
if (queue.length) {
var doc = new XMLHttpRequest();
doc.onreadystatechange = function() {
if (doc.readyState == XMLHttpRequest.DONE) {
if (doc.status == 200) {
queue[0].myCallback(doc.responseText);
} else {
fonctionPourAnalyserLErreur(url);
}
// done now so remove this one from the queue
// and start the next one
queue.shift();
processNextInQueue();
}
}
doc.open(queue[0].myMethod, queue[0].myUrl);
doc.send();
}
}
processNextInQueue();
The idea is that you fire off the first ajax call and then you just return. When the readystatechange shows it is done, you process the results and then fire off the next one. All the while the ajax call is in process, the javascript engine is free to service other events and do other things (that's the key to handling an asynchronous operation like an ajax call).
In this line: queue[0].myCallback(data), for debugging purposes (and to prevent errors from breaking your site) I would change to the following:
var current = queue.shift();
if(typeof current.myCallback == 'function'){
current.myCallback(data)
} else {
// for now, log it
console.log('Passed callback is not a function');
}
Also, have you tried just passing an anonymous function to make sure it's not a scope/function hoisting issue?
function loadUserDetails() {
var url = "https://someurl.com";
doWebRequest("GET", url, "", function(dataFromServer){
console.log("data received from server: "+JSON.stringify(dataFromServer));
});
}
Related
I'm working on a firefox extension and using browser.webRequest.onBeforeRequest.addListener.
I need to redirect or release the webRequest until I have information back from calls made within the handler.
I used to use synchronous ajax, but the page was blocked for too long when the network was poor. If the request time exceeds 5 seconds, I want to cancel the ajax request. But it seems impossible to set a timeout on a synchronous call.
These days I try to use asynchronous ajax and use the methods(return new Promise) provided in https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/onBeforeRequest. I tried several days but failed.
My codes:
var LOOKUP_URL = "https://a.b.c.com/";
var urlLookup = new UrlLookup(LOOKUP_URL);
var blockList = [];
browser.webRequest.onBeforeRequest.addListener(redirectAsync, {urls: ['<all_urls>']},, ["blocking"]);
function redirectAsync(details) {
var url = details.url;
return new Promise(function(resolve,reject){
var urlLookupResult = urlLookup.check(url, LookupComplete);
if(urlLookupResult.result){
var redirectUrl = urlLookupResult.url;
resolve({redirectUrl})
}
})
}
function LookupComplete(url, data, error){
if(data.result){
blockList.push(url);
localStorage.setItem("blockList",JSON.stringify(blockList));
return {
result: true,
url: "https://aaa.bbb.com/alerts.php?url=" + url;
}
}else {
return null
}
}
codes in urlLookup.js:
function UrlLookup(domain) {
function check(url, callback) {
var data;
var http_request = new XMLHttpRequest();
var timeId = window.setTimeout(function(){
http_request.abort();
},5000)
http_request.open("GET", domain + 'url='+url);
try {
http_request.send();
http_request.onreadystatechange = function(){
if(http_request.readyState == 4 && http_request.status == 200){
window.clearTimeout(timeId);
data = JSON.parse(http_request.response);
return callback(url, data);
}
}
} catch (e){
return callback(url, null, "Error");
}
};
return {
check: check
};
}
How should I modify it?
So basically I have an ajax function pretty standard one. Like so:
function ajax_call(rest_req, url, success_callback, fail_callback) {
// if (request_in_progress)
// return;
// request_in_progress = true;
var xhttp = new XMLHttpRequest;
xhttp.onreadystatechange = function () {
if (this.readyState == 4) {
// request_in_progress = false;
if (this.status == 200) {
success_callback(this);
}
else {
fail_callback(this);
}
}
};
xhttp.open(rest_req, url, true);
xhttp.send();
}
When I use the ajax function this way:
(function() {
function setup() {
ajax_call("GET", "url1", function(xhttp) {
response = JSON.parse(xhttp.responseText);
if (response["error"] != 100)
document.getElementById('url1-reading').innerHTML = '---';
else
document.getElementById('url1-reading').innerText = response["response"];
},
function() {}
);
ajax_call("GET", "url2" , function(xhttp) {
response = JSON.parse(xhttp.responseText);
if (response["error"] != 100)
document.getElementById('url2-reading').innerHTML = '---';
else
document.getElementById('url2-reading').innerText = response["response"];
},
function() {}
);
console.log('Refresh');
}
setInterval(setup, 1000);
})();
This code behaves differently than what I expect. When I run this code, there are some times when the results that were suppose to go to url1 success_callback goes inside url2's success_callback.
To put another way the response variable inside url1 ajax_call is what I expected to show up as response variable for url2. So in effect the ajax_call seem to not know what success_callback is for what even though I explicitly pass it in as a parameter.
I'm coming from a C++ background so this is a difficult concept to grasp. How do I do this the right way? I hope my question is clear. Please tell me what is not clear so I can clarify.
The way you declare it, response is a global variable. Try changing response = to let response =
I've got some code that checks if a user is connected to the internet. It works perfectly, but in the application I have things need to move quickly. In the case that they're connected to a really slow or non-functioning network my statement takes a long time to evaluate to false and really slows things down. If they're connected it retrieves the 1px image in no time at all.
My question is. How can I modify this function to return "false" if it takes more than a second to run the XML request? Can't seem to find an easy way to do this, but I am new to javascript so sorry if I'm missing something obvious.
function doesConnectionExist() {
var xhr = new XMLHttpRequest();
var file = "/assets/LuPixel.png";
var randomNum = Math.round(Math.random() * 10000);
xhr.open('HEAD', file + "?rand=" + randomNum, false);
try {
xhr.send();
if (xhr.status >= 200 && xhr.status < 304) {
return true; //alert("TRUE");
} else {
return false; //alert("FALSE");
}
} catch (e) {
return false; //alert("FALSE");
}
}
UPDATE: thought I needed a little more context.
What I'm trying to do with the above function is test whether a connection exists, and then access a certain URL if it does, if it doesn't, store some info in a local array. As soon as I add timeout however, it breaks the existing functionality.
ANother part that may be complicating things is that I'm actually loading up all survey questions inintially, then using javascript to cycle out which ones display. At the end of the survey it either submits results and refreshes the page (if connected to the internet) or just shows the first question again and continues storing results in the "responses" object (if not connected to the internet) additionally each time a question is clicked it also attempts to use the above doesConnectionExists() function to determine whether to submit results as the javascript transitions to the next question. the questions variable below contains the number of questions left to show
Here is all the javascript from the page.
<script>
//prevents scrolling
document.body.addEventListener('touchmove', function(event) {
event.preventDefault();
}, false);
var questions = {{count($questions)}};
var isIOS = ((/iphone|ipad/gi).test(navigator.appVersion));
var myDown = isIOS ? "touchstart" : "mousedown";
var myUp = isIOS ? "touchend" : "mouseup"
var responses = '';
// Hide every div except the first one
$('.survey-container:not(:first)').hide();
$('button').bind(myUp, finish);
//$('button').bind('touchcancel', cancel);
function finish(){
var answerId = this.id;
var survey_id = {{$survey->id}};
var location_id = {{$location_id}};
//CYCLE THROUGH QUESTIONS (via survey-container)
// Hide the current div when we click the link
$(this).parent().parent().next().show();
$(this).parent().parent().hide();
responses += answerId+'.'+Math.floor(Date.now() / 1000)+' ';
//add responses
if(doesConnectionExist() == true) { //old navigator.onLine == true
//send response and reset responses
new Image().src = '/updateresponses?location_id='+location+'&survey_id='+survey_id+'&responses='+responses;
responses = '';
}
//submit and reset responses here
//reset responses if submitted with: responses = '';
questions = questions-1;
if(questions == 0) {
$('.survey-container:first').show();
questions = {{count($questions)}};
if(doesConnectionExist() == true) {
$('.darken, .thankyou').show();
//submit results after a short timeout for thankyou message
setTimeout(function() {
new Image().src = '/updateresponses?location_id='+location+'&survey_id='+survey_id+'&responses='+responses;
location.reload();
//window.location.assign('/updateresponses?location_id='+location+'&survey_id='+survey_id+'&responses='+responses);
}, 800);
} else {
$('.darken, .thankyou').show();
setTimeout(function() {
$('.darken, .thankyou').fadeOut(400);
}, 1200);
}
} else {
$('.darken, .thankyou').show();
setTimeout(function() {
$('.darken, .thankyou').fadeOut(400);
}, 1200);
}
}
function doesConnectionExist() {
var xhr = new XMLHttpRequest();
var file = "/assets/lupoll_light.png";
var randomNum = Math.round(Math.random() * 10000);
xhr.open('HEAD', file + "?rand=" + randomNum, false);
try {
xhr.send();
if (xhr.status >= 200 && xhr.status < 304) {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
}
</script>
You can use async requests with timeout and abort if time limit exceeded.
var xmlhttp = getXmlHttp()
xmlhttp.open("POST", "/someurl", true); // true - activate async request method
xmlhttp.onreadystatechange=function(){
if (xmlhttp.readyState != 4) return
clearTimeout(timeout) // clear timeout on readyState 4
if (xmlhttp.status == 200) {
// all ok
...
alert(xmlhttp.responseText);
...
} else {
handleError(xmlhttp.statusText) // error callback
}
}
xmlhttp.send("a=5&b=4");
// 10 second timeout
var timeout = setTimeout( function(){ xmlhttp.abort(); handleError("Time over") }, 10000);
function handleError(message) {
// error callback function
...
alert("error: "+message)
...
}
Add timeout to the xhr object:
xhr.timeout = 1000;
You can specify the timout for the request:
xhr.timeout = 1000;
Besides setting the timeout, you should make the function asynchronous. That way you can allow a bit longer timeout if you with, and the application will still not be sluggish.
function doesConnectionExist(callback) {
var xhr = new XMLHttpRequest();
var file = "/assets/LuPixel.png";
var randomNum = Math.floor(Math.random() * 10000);
xhr.timeout = 1000;
xhr.onreadystatechange = function() {
if (xhr.status >= 200 && xhr.status < 304) {
callback(true);
} else {
callback(false);
}
};
xhr.open('HEAD', file + "?rand=" + randomNum, true);
xhr.send();
}
I have an ajax call where I used jQuery.ajax() to make a request to an mvc action. This all worked fine. However due to some forms having a file control I changed it from using jQuery.ajax() to using the XMLHttpRequest to send it using the HTML5 File API.
Since making this change the MVC action method no longer see's it as an ajax request. Using Fiddler2 I have noticed that it no longer adds the "X-Requested-With: XMLHttpRequest" to the request and I assume this is the problem.
The form I am trying to send does not have a file input in it, only normal textboxes etc, but I was trying to keep the method generic to deal with both. The following is the code I am using to send the ajax request:
// get the edit tender form
var $Form = $Button.closest('form');
var Url = $Form.attr('action');
var AjaxRequestObject = new XMLHttpRequest();
var FormDataToSend = new FormData();
$Form.find(':input').each(function () {
if ($(this).is('input[type="file"]')) {
var files = $(this)[0].files;
if (files.length > 0) {
FormDataToSend.append(this.name, files[0]);
}
} else {
FormDataToSend.append(this.name, $(this).val());
}
});
AjaxRequestObject.open('POST', Url, true);
AjaxRequestObject.onreadystatechange = function () {
if (AjaxRequestObject.readyState == 4) {
// handle response.
if (AjaxRequestObject.status == 200) {
if (!AjaxErrorExists(AjaxRequestObject.responseText, )) {
alert("success");
console.log(AjaxRequestObject.responseText);
}
else {
alert('failure');
}
}
else {
alert('failure');
}
}
};
AjaxRequestObject.send(FormDataToSend);
This code was provided following a problem I had which Darin Dimitrov provided the solution to, so I could send the file inputs by ajax.
Any ideas why this request would not send the header for an ajax call?
X-Requested-With is automatically added by jQuery. You can just as easily add it yourself with AjaxRequestObject.setRequestHeader(). Docs
I was having troubles with detecting if my request was ajax. So, maybe this sample will save someone a minute or two:
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', URL, true); // `true` for async call, `false` for sync.
// The header must be after `.open()`, but before `.send()`
xmlhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlhttp.onreadystatechange = function() {
// 4th state is the last:
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { ... }
};
xmlhttp.send();
Tested with Flask.
You can override natively all XMLHttpRequest.open method calls and add in it X-Requested-With header like:
(function () {
// #author https://github.com/stopsopa jfdsa78y453cq5hjfd7s877834h4h3
if (window.XMLHttpRequest.prototype.onOpen) {
return console.log('XMLHttpRequest.onOpen is already defined');
}
function over(method, on, off) {
var old = window.XMLHttpRequest.prototype[method];
if (!old.old) {
var stack = [];
window.XMLHttpRequest.prototype[on] = function (fn) {
if (typeof fn === 'function') {
stack.push(fn);
}
}
window.XMLHttpRequest.prototype[off] = function (fn) {
for (var i = 0, l = stack.length ; i < l ; i += 1 ) {
if (stack[i] === fn) {
stack.splice(i, 1);
break;
}
}
}
window.XMLHttpRequest.prototype[method] = function () {
var args = Array.prototype.slice.call(arguments);
var ret = old.apply(this, args);
for (var i = 0, l = stack.length ; i < l ; i += 1 ) {
stack[i].apply(this, args);
}
return ret;
}
window.XMLHttpRequest.prototype[method].old = old;
}
}
over('open', 'onOpen', 'offOpen')
XMLHttpRequest.prototype.onOpen(function () {
this.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
});
}());
I wrote the following :
function ao(){
this.count=0;
this.flag=0;
this.tmr=0;
var self = this;
this.make=function(){
//log("before: "+this.url+" "+this.xhr);
self.xhr = (window.XMLHttpRequest)
? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
//log("after: "+this.xhr);
}
this.request = function (method, url, sendStr, delay){
this.delay=delay;
if(delay && self.tmr==0){
self.start();
}
if(self.flag==0){
this.method = method;
this.url = url;
this.sendStr = sendStr;
self.make();
this.xhr.open(method, url, true);
this.xhr.onreadystatechange = this.stateChange;
this.xhr.onabort=this.rrr;
this.xhr.onerror=this.rrr;
this.xhr.setRequestHeader("Cache-Control","no-cache");
this.xhr.send(sendStr);
}
};
this.repeat=function(){
if(this.flag==0){
this.flag=1;
this.count++;
this.xhr.open(self.method, self.url+"?"+this.count, true);
this.xhr.onreadystatechange = this.stateChange;
this.xhr.onabort=this.rrr;
this.xhr.onerror=this.rrr;
this.xhr.setRequestHeader("Cache-Control","no-cache");
this.xhr.send(self.sendStr);
}
return 0;
}
this.stop=function(){
window.clearInterval(this.tmr);
this.tmr=0;
this.flag=0;
}
this.start =function(){
self.tmr=window.setInterval(function(){self.repeat();},self.delay);
}
this.stateChange = function(){
if (self.xhr.readyState <= 1){
return;
self.log("404 errors");
} else {
if (self.xhr.readyState == 4 && self.xhr.status == 200){
self.resp = self.xhr.responseText;
if (self.callback != null)
self.callback(self.xhr.readyState, self.xhr.status);
else {
if (self.getHTML) {
self.getHTML(self.resp);
this.xhr=null;
} else {
if (self.xhr.readyState == 4 && self.xhr.status == 200){
self.parseJSON();
self.traverse();
this.ro=null;
this.xhr=null;
}
}
}
}
}
self.flag=0;
return 0;
};
and in windows ff there is a memory leak. I spent days trying to fix it, but I'm stumped.
The following works :
var x=new ao();
ao.request("POST","/cgi-bin/sdf.cgi","text",1000)
and after every 1000 miliseconds if previous request is done, it makes new request.
Developers should also take precautions when it comes to using the
onreadystatechanged event of an XMLHttpRequest object. If the handler
is a closure that closes over a reference to the same XMLHttpRequest
object, another circular dependency can be created. This isn't
necessairly detected by the above tool because the object is not part
of the DOM.
Link