xmlhttprequest timeout / abort not working as expected? - javascript

Here is my AJAX function:
/**
* Send an AJAX request
*
* #param url The URL to call (located in the /ajax/ directory)
* #param data The data to send (will be serialised with JSON)
* #param callback The function to call with the response text
* #param silent If true, doesn't show errors to the user
* #param loader The element containing "Loading... Please wait"
*/
AJAX = function(url,data,callback,silent,loader) {
var a,
attempt = 0,
rsc = function() {
if( a.readyState == 4) {
if( a.status != 200) {
if( a.status > 999) { // IE sometimes throws 12152
attempt++;
if( attempt < 5)
send();
else if( !silent) {
alert("HTTP Error "+a.status+" "+a.statusText+"<br />Failed to access "+url);
}
}
else if(!silent) {
alert("HTTP Error "+a.status+" "+a.statusText+"\nFailed to access "+url);
}
}
else {
callback(JSON.parse(a.responseText));
}
}
},
to = function() {
a.abort();
attempt++;
if( attempt < 5)
send();
else if( !silent) {
alert("Request Timeout\nFailed to access "+url);
}
};
data = JSON.stringify(data);
var send = function() {
if( loader && attempt != 0) {
loader.children[0].firstChild.nodeValue = "Error... retrying...";
loader.children[1].firstChild.nodeValue = "Attempt "+(attempt+1)+" of 5";
}
a = new XMLHttpRequest();
a.open("POST","/ajax/"+url,true);
a.onreadystatechange = rsc;
a.timeout = 5000;
a.ontimeout = to;
a.setRequestHeader("Content-Type","application/json");
a.send(data);
};
send();
};
The general idea is to attempt the request up to five times. Sometimes IE fails with an unusual HTTP error (12xxx), and sometimes the server may fail to respond.
The problem I'm having is that the abort() call doesn't appear to be aborting the connection. To test, I made a simple PHP script:
<?php
sleep(60);
touch("test/".uniqid());
die("Request completed.");
?>
The touch() call creates a file with the current uniqid() - by looking at the modification time I can see the time the sleep(60) ended.
Expected behaviour:
The request is sent
After five seconds, the text changes to "Error... Retying... Attempt 2/5"
Repeat the above up until Attempt 5/5, then fail.
The five calls to the PHP file are aborted, and either there will be five files in the "test" folder, spaced 5 seconds apart, or there will be none because ignore_user_abort is off.
Observed behaviour (in IE9):
The request is sent
The attempt text appears and changes as it should
After five attempts, the error message is displayed
I am unable to load any pages for five whole minutes.
On the server, there are five files spaced one minute apart
I don't know what to make of this, because on the server side Requests 3, 4 and 5 are being sent minutes after the "Timeout" error message is shown on the browser.
If it makes any difference, the page making the AJAX calls is in an iframe. Reloading the iframe (using iframe.contentWindow.location.reload() does NOT fix the issue, it still waits for those five requests to go through.
Why is this happening? How can I fix it?
EDIT: I've run the test again using Developer Tools to monitor network activity. The result is:
URL Method Result Type Received Taken Initiator
/ajax/testto (Aborted) 0 B < 1 ms (Pending...)
/ajax/testto (Aborted) 0 B 125 ms (Pending...)
/ajax/testto (Aborted) 0 B 125 ms (Pending...)
/ajax/testto (Aborted) 0 B 125 ms (Pending...)
/ajax/testto (Aborted) 0 B 124 ms (Pending...)

The problem seems to be that timeout and ontimeout aren't yet part of some implementations:
var hasTimeout = 'timeout' in new XMLHttpRequest(); // false
At least, it's not in Chrome 16 or Firefox 7. And, this should return true given the requirements:
The timeout attribute must return its value. Initially its value must be zero.
Both have been part of the XHR 2 spec since Sept 7, 2010. But, as the "Level 2" spec has been been around since Feb 25, 2008 and is still a "Working Draft," there's not really a guarantee that any implementation would be up-to-date with the spec.
Without those available to you, you can try instead using onabort and setTimeout (as you stated in your comment):
// snip
to = function() {
attempt++;
if( attempt < 5)
send();
else if( !silent) {
console.log("Request Timeout\nFailed to access "+url);
}
};
// snip
var send = function() {
if( loader && attempt != 0) {
loader.children[0].firstChild.nodeValue = "Error... retrying...";
loader.children[1].firstChild.nodeValue = "Attempt "+(attempt+1)+" of 5";
}
a = new XMLHttpRequest();
a.open("POST","/ajax/"+url,true);
a.onreadystatechange = rsc;
setTimeout(function () { /* vs. a.timeout */
if (a.readyState < 4) {
a.abort();
}
}, 5000);
a.onabort = to; /* vs. a.ontimeout */
a.setRequestHeader("Content-Type","application/json");
a.send(data);
console.log('HTTP Requesting: %s', url);
};
// snip
Example: http://jsfiddle.net/AmQGM/2/ -- The ?delay=2 should finish, while the ?delay=10 expires its 5 tries.

When running your code, I got the error c00c023f. When I googled it, it came up with this answer:
http://www.enkeladress.com/article.php/internetexplorer9jscripterror
This sounds very similar to what you're experiencing.
Here's a SO question which has the same problems, with a solution (also based on the above link, but with additional information): IE 9 Javascript error c00c023f

Before aborting the request, try setting
a.onreadystatechange = function() {};
after that, also add a check around calling abort:
if( a.readyState > 0 && a.readyState < 4 ) {
a.abort();
}

I had the same problem, it turned out to be the issue with PHP session.
If the session is open, all other requests from the same session must wait.
<?php
session_write_close();
sleep(60);
touch("test/".uniqid());
die("Request completed.");
?>
This, of course, would not help you if you haven't started the session :)

Related

Unexpected recursions in safari browser

I've tried to implement long polling chat. My chat works correctly, except one browser - safari.
I get new message by this code (here only part, just for understanding recursion):
let errorCounter = 0;
let getChatText = function () {
let options = {
//headers and other configuration, which needed for XMLHttpRequest
};
let responseHandler = function(responseCode, body) {
switch (responseCode) {
//handle response codes
case 200:
errorCounter = 0; //on success response
// other codes handle here
default:
errorCounter++;
}
if (errorCounter >= 30) { // 30 just for testing
endChatSession(true);
} else {
getChatText(); //recursion
}
};
requestCall(options, false, responseHandler);
};
If in some moment I reload the page - it doesn't stop, but tryis to call many times (30 in my case) and endChatSession on too many errors. But in other browsers (chrome, firefox etc.) - its immediately stops recursion, and reload the page.
Here is a console output, before safari page reload (preserve log):
Question: How can I stop recursion on page reload in safari, because it is not a reason to endChatSession

How to prevent WebLOAD wlHttp module from aborting round when receiving non-200 status code?

I've been recently tasked with working in WebLOAD in order to test the functionality of an API. Alternatives to WebLOAD can't be used for the task due to the project's requirements.
Some of the test cases I've been tasked with writing involve submitting a malformed request and ensuring that a 400 response code was returned. My problem is that every time a non-200 response is received, wlHttp throws an error in the console and aborts the current round.
I've tried surrounding the wlHttp.Get call with a try/catch but that didn't work. Any help would be very much appreciated, as judging from this document it seems like it should be possible to continue after receiving a non-200 status code.
Below is an MVP similar to the code I'm writing for my test cases. In the console output (below the MVP) you can see that "1" was logged however execution stopped immediately after the error concerning the 400 was logged with console.log("2") never being ran.
function InitAgenda() {
wlGlobals.GetFrames = false;
wlGlobals.SaveSource = true;
wlGlobals.SaveHeaders = true;
}
/***** WLIDE - ID:5 - JavaScript *****/
wlHttp.ContentType = "application/json";
console.log("1");
wlHttp.Get("https://httpstat.us/400");
console.log("2");
// END WLIDE
0.00 *** Script Execution Start Time: Thu Aug 15 17:15:56 2019 ***
0.33 Start round 1 (1 of 1)
0.34 1
0.76 400 Bad request. Requested URL: https://httpstat.us/400. in main_agenda at line 15
1.85 End round 1 (1 of 1)
2.06 *** Script Execution End Time: Thu Aug 15 17:15:58 2019 ***
2.06 Test Failed: The number of errors is equal to or greater than 1.
You should use wlHttp.ReportHttpErrors=false.
Use document.wlStatusNumber to check the response.
See example:
function InitAgenda()
{
wlGlobals.GetFrames = false;
wlGlobals.SaveSource = true;
wlGlobals.SaveHeaders = true;
}
/***** WLIDE - ID:5 - JavaScript *****/
wlHttp.ContentType = "application/json";
console.log("1");
wlHttp.ReportHttpErrors=false
wlHttp.Get("https://httpstat.us/400");
if (document.wlStatusNumber != 400) {
WarningMessage("hmm, expected 400 but got " + document.wlStatusNumber + ", " + document.wlStatusLine)
}
console.log("2");

Session doesn't work

So, I'm trying to make a web application. During registration, I require from the user to enter his working experience. While at it, I test if the time span in which the user has been working overlaps with previously entered time spans, just to warn him of it. I use controller and JS script for this.
This is my controller method:
public void TimeCheck()
{
string time = Request.QueryString.ToString();
using (ITExpertsContext db = new ITExpertsContext())
{
int id = db.Users.FirstOrDefault(x => x.Email.Equals(User.Identity.Name)).UserId;
List<WorkingAt> currentHistory = db.WorkingAts.Where(x => x.UserId == id).ToList();
TimeFrame frame = new TimeFrame();
frame.Since = DateTime.Parse(time.Split('&')[0].Split('=')[1]);
frame.Until = DateTime.Parse(time.Split('&')[1].Split('=')[1]);
foreach (WorkingAt w in currentHistory)
{
if ((w.Since < frame.Until && w.Until > frame.Until) || (w.Since < frame.Since && w.Until > frame.Since))
{
Session["Time"] = "1";
return;
}
}
Session["Time"] = "0";
}
}
And this is my JS method from the view:
function TimeCheck() {
var request = new XMLHttpRequest();
request.open("GET", "/account/TimeCheck/?since=" + $("#Since").val() + "&until=" + $("#Until").val());
request.send();
request.onreadystatechange = function () {
if (request.status == 200 & request.readyState == 4) {
if (sessionStorage.Time == "1") {
var choice = confirm("The time you selected overlaps with your previous working experience. Do you still want to add it?");
if (choice == true) {
return true;
}
else {
return false;
}
}
else {
return false;
}
}
}
}
Problem with all of this is my session does not change at all, aka does not exist. It works well when I set it from JS (for testing purpose). What am I doing wrong?
I'm using latest VS 2017 ver 15.7, so I'm assuming my MVC is 5.2.3, if that's even needed to be known.
EDIT:
The code in controller reaches to the end without any problems. I did the debug of controller line by line and it works as I designed it. Just before "return" I tried reading session["Time"] and it shows it's there. Once the execution goes back to the JS the browser does not show there's a session data under the key "Time", and therefore my "if(sessionStorage.Time == "1")" statement is pointless.
sessionStorage is defined in the HTML5 spec. which is client side only, so setting a Session variable on the server has no effect on sessionStorage client side.
See: Does HTML5 sessionStorage exist on the server or client?

How to loop through GET/POST calls sequentially (waiting for previous) return?

I'm writing a Tampermonkey script for a web page and trying to extract data from other pages.
I'm trying to make a function that has a loop inside that goes thru a list, llcList, and retrieves data from ajax method GET, but would like to wait for to finish one request before going to second one.
Bonus would be if I could make it wait some extra time.
What should happen:
send request for a llcList[0]
get return data, process it
wait some time
send new request for a llcList[1]
Is this possible? I tried few methods, every time loop send all requests not a second apart. :
function F_Company_LLC(){
for (i = 0; i < llcList.length;i++) {
if(llcList[i][2]=="lab"){
//run function 0
//break;
}
else if(llcList[i][2]=="shop"){
//run function 1
//break;
}
else{
F_GET_CompData(llcList, llcList[i][1],i,function(result){
console.log(result);
});
}
}}
function F_GET_CompData(F_GET_CompData_list, CompID, F_GET_CompData_row, callback){
$.ajax({
method : "GET",
url: base_link+"/company/edit_company/"+CompID,
beforeSend: function(){runningRequest++;},
success: function(data){
//data processing
runningRequest--;
},
error: function() {console.log("Get_ComData");}
});
callback(runningRequest);}
This is a common scenario. Note that it's often unnecessary to process the calls sequentially though. It's usually adequate to just send context with the ajax calls and piece everything together as it comes in semi randomly, as shown in this answer.
One way to force sequential behavior is to chain calls via the complete function. Here is fully functional code that demonstrates the process. To use, paste it into your browser console while on a Stack Overflow page. :
var listO_pages = ["q/48/", "q/27/", "q/34/", "q/69/", "badpage"];
var numPages = listO_pages.length;
getPageN (0); //-- Kick off chained fetches
function getPageN (K) {
if (K >= 0 && K < numPages) {
let targPage = listO_pages[K];
$.ajax ( {
url: "https://stackoverflow.com/" + targPage,
context: {arryIdx: K}, // Object Helps handle K==0, and other things
success: processPage,
complete: finishUpRequest,
error: logError
} );
}
}
function processPage (sData, sStatus, jqXHR) {
//-- Use DOMParser so that images and scripts don't get loaded (like jQuery methods would).
var parser = new DOMParser ();
var doc = parser.parseFromString (sData, "text/html");
var payloadTable = doc.querySelector ("title");
var pageTitle = "Not found!";
if (payloadTable) {
pageTitle = payloadTable.textContent.trim ();
}
var [tIdx, tPage] = getIdxAndPage (this); // Set by `context` property
console.log (`Processed index ${tIdx} (${tPage}). Its title was: "${pageTitle}"`);
}
function finishUpRequest (jqXHR, txtStatus) {
var nextIdx = this.arryIdx + 1;
if (nextIdx < numPages) {
var tPage = listO_pages[nextIdx];
//-- The setTimeout is seldom needed, but added here per OP's request.
setTimeout ( function () {
console.log (`Fetching index ${nextIdx} (${tPage})...`);
getPageN (nextIdx);
}, 222);
}
}
function logError (jqXHR, txtStatus, txtError) {
var [tIdx, tPage] = getIdxAndPage (this); // Set by `context` property
console.error (`Oopsie at index ${tIdx} (${tPage})!`, txtStatus, txtError, jqXHR);
}
function getIdxAndPage (contextThis) {
return [contextThis.arryIdx, listO_pages[contextThis.arryIdx] ];
}
This typically outputs:
Processed index 0 (q/48/). Its title was: "Multiple submit buttons in an HTML form - Stack Overflow"
Fetching index 1 (q/27/)...
Processed index 1 (q/27/). Its title was: "datetime - Calculate relative time in C# - Stack Overflow"
Fetching index 2 (q/34/)...
Processed index 2 (q/34/). Its title was: "flex - Unloading a ByteArray in Actionscript 3 - Stack Overflow"
Fetching index 3 (q/69/)...
Processed index 3 (q/69/). Its title was: ".net - How do I calculate someone's age in C#? - Stack Overflow"
Fetching index 4 (badpage)...
GET https://stackoverflow.com/badpage?_=1512087299126 404 ()
Oopsie at index 4 (badpage)! error Object {...
-- depending on your Stack Overflow reputation.
Important: Do not attempt to use async: false techniques. These will just: lock up your browser, occasionally crash your computer, and make debug and partial results much harder.

navigator.notification.confirm() - firing multiple times

Hi I have a loop in JSON to retry connection 3x before firing an error however sometimes I have 3-4 JSON requests and all of them can trough an errors so I have 3 alerts in my phonegap app.
EG.
function showJSONerror(xhr, status) {
if (xhr.status === 0 && localStorage["TmpAlertShow"] !== true) {
navigator.notification.confirm('Connection error.\n Verify your network connection and try again.', confirmAction, 'Woops...','Try Again, Close');
localStorage["TmpAlertShow"] = true;
function confirmAction(button) {
if (button == 1) { localStorage["TmpAlertShow"] = false; showPage(localStorage["TmpWebView"]) }
if (button == 2) { localStorage["TmpAlertShow"] = false; return false; }
}
}
I'm trying to find the way to close previous alert via JS or record the sate if the alert is already fired and not closed (prevent to display a multiple alerts)
Thanks
You could try something like setting a global variable that is the count of requests currently running and then in your error function, say if the count is greater than 0 store the result in a global array and if not then process the errors for display.
Example:
var callsRunning = 0;
var callStatuses = [];
When running a call add:
callsRunning++;
When a call is done:
callsRunning--;
in your error function:
if(callsRunning > 0) {
callStatuses.push(/*whatever you want to collect*/);
}
else {
/*compile statuses for display of error*/
}
i've had a similar problem before
I used to resolve it http://underscorejs.org/#after
maybe give this a shot?

Categories

Resources