Javascript: Using asynchronous AJAX synchronously - javascript

First, I've looked at related SO questions, and didn't find much in the way of a suitable answer, so here goes:
I've been working on an HTML/Javascript page that acts as a UI to a back-end server. I made some pretty good strides in completing it, all while using synchronous calls in AJAX (aka var xmlhttp = new XMLHttpRequest(); xmlhttp.open(type, action, false);), but have now come to find out that Mozilla apparently doesn't like synchronous requests, and has therefore deprecated some much-needed functionality from them.
To quote https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest:
Note: Starting with Gecko 11.0 (Firefox 11.0 / Thunderbird 11.0 / SeaMonkey 2.8), as well as WebKit build 528, these browsers no longer let you use the responseType attribute when performing synchronous requests. Attempting to do so throws an NS_ERROR_DOM_INVALID_ACCESS_ERR exception. This change has been proposed to the W3C for standardization.
So that's great. I'm about to need to change the response type conditionally, but it won't work. It is now my intention to wrap an AJAX asynchronous request in something that will simulate synchronicity.
The following is a generic "make web request" function that my code uses, that I've started adapting to work for my purposes. Unfortunately, it isn't working quite like I'd hoped.
var webResponse = null;
function webCall(action, type, xmlBodyString) {
console.log("In webCall with " + type + ": " + action);
webResponse = null;
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function()
{
if (xmlhttp.readyState == 4)
{
if (xmlhttp.status == 200) {
webResponse = xmlhttp.responseXML;
} else {
var statusTxt = xmlhttp.statusText;
if (statusTxt == null || statusTxt.length == 0) {
statusTxt = "An Unknown Error Occurred";
}
throw "ERROR " + xmlhttp.status + ":" + statusTxt;
}
}
}
xmlhttp.open(type, action, true);
if (xmlBodyString == null) {
xmlhttp.send();
} else {
xmlhttp.setRequestHeader("Content-Type", "text/xml");
xmlhttp.send(xmlBodyString);
}
for (var i = 0; i < 20; i++) {
if (webResponse != null) {
break;
}
window.setTimeout(nop, 250);
}
if (webResponse == null) {
throw "Waited 5 seconds for a response, and didn't get one.";
}
console.log("Responding with " + webResponse);
return webResponse;
}
function nop() {
}
So, I thought this was pretty straight-forward. Create a global variable (in retrospect, it probably doesn't even have to be global, but for now, w/e), set up the onreadystatechange to assign a value to it once it's ready, make my asynchronous request, wait a maximum of 5 seconds for the global variable to be not null, and then either return it, or throw an error.
The problem is that my code here doesn't actually wait 5 seconds. Instead, it immediately exits, claiming it waited 5 seconds before doing so.
I made a fiddle, for what it's worth. It doesn't work in there either.
http://jsfiddle.net/Z29M5/
Any assistance is greatly appreciated.

You can't do it. Stick to asynchronous requests. Callback hell sucks, but that's what you get in event-driven systems with no language support.
There is simply no way to simulate synchronous code in plain JavaScript in browsers at the moment.
If you could severely limit your supported set of browsers (pretty much just Firefox at the moment AFAIK) you can get synchronous-looking code by using generators.
There are also languages that compile into JS and support synchronous-looking code. One example I can think of (from a few years ago) is this: https://github.com/maxtaco/tamejs

Firstly, for all the pain, using asynchronous code asynchronously is the way to go. It takes a different approach, that's all.
Secondly, for your specific question, this is what your 'delay' loop is doing:
For twenty iterations
if we've had a response, break
set a timeout for 250ms
go round again
(The entire for loop completes all 20 iterations immediately. You won't have a response)
.
.
.
after 250ms
execute the first setTimeout callback, which is nop
execute the second...
I can't think of a quick way to fix this, other than putting your processing code in the AJAX call back, which is where it should be for async code anyway.

Why not create an array of requests and just pop them off one-by-one when you get the response from the previous ajax call.

Related

How to make requests with xmlhttprequest until response is ready?

I'm trying to make a request to a route which makes a query to an API and if the API has the data, the response is to render another website with the API data. But if the data is not ready yet, since it is still processing, the route returns a string "not finished yet".
What I wish to do is: make a get request and if the response is "not finished yet" wait for 5 seconds and do the request again until the response is the data. After it, the script would open the window with the new page with the data loaded.
Here is what I have already made:
job_id = document.querySelector("#job_id").getAttribute("value")
code = document.querySelector("#code").getAttribute("value")
​
var myRequest = new XMLHttpRequest();
​myRequest.open('GET', `http://127.0.0.1:5000/status/${job_id}/${code}`);
​myRequest.onreadystatechange = function () {
if (myRequest.readyState === 4 && myRequest.responseText != 'not finished yet') {
window.location = `http://127.0.0.1:5000/status/${job_id}/${code}`
}
};
If anyone knows if it works or knows a better way to deal with that, I'd appreciate your help.
Thanks in advance.
Solution:
After some hours, I finnaly found a way to handle it. Still don't know if it is the best way.
function search() {
job_id = document.querySelector("#job_id").getAttribute("value")
code = document.querySelector("#code").getAttribute("value")
var myRequest = new XMLHttpRequest();
myRequest.open('GET', `http://127.0.0.1:5000/status/${job_id}/${code}`);
myRequest.send();
myRequest.onreadystatechange = function () {
if (myRequest.readyState === 4 && myRequest.responseText === 'not finished yet') { setTimeout(function () {search();}, 5000)
}
else if(myRequest.readyState === 4 && myRequest.responseText != 'not finished yet')
{ window.location = `http://127.0.0.1:5000/status/${job_id}/${code}`}}
}
search()
I use var option = {}; as a global object to handle OOP (object oriented programming).
What you want to do is when you need to define something give it a prefix for the function and an identifier so you can avoid conflicts.
You posted some code so at 1 reputation and decent formatting you're doing a lot better than most starting at 1, kudos. Let's say you're working with a job ID of 79. So you'll want to define the following:
option.job_79 = 1;
Now I assigned the sub-object a 1 as a status, it's initialized. Since the option object is global scope you can have another call to your ajax() function and without it knowing that another ajax() function is already running you simply check for the typeof option.job_79 instead!
Some recommendations:
If you're enthusiastic about programming you'll eventually want to merge all your AJAX functions in to one single well refined function, it'll not only greatly simplify your code the up-front cost will save you and the earlier the better (though the more you'll have to refine it over time).
Also avoid the evils of frameworks and libraries. People make such a big deal about them but a few years later when you want to update you can't without spending days or weeks refactoring code. I've never had to refactor code using only pure JavaScript for any other reason other than my experience level, never because of a browser update. There are numerous other benefits that are hidden along that path and most people aren't aware of that.

EventListeners, onreadystatechange, or jQuery for AJAX calls?

I'm making calls to the OpenWeatherMap API to get a weather forecast JSON object. I used 3 different javascript methods that are called when someone enters a zipcode in the zipweather html id element and presses submit or enter, calling zipWeather() and basically pasting the zipcode to the end of the api address which then sends back the data on that zipcode.
They all work fine. They all get a city name and temperature that is converted into Fahrenheit.
They all use a callback in the error handler to the function itself in case of failure. The first one uses a 5 second timeout callback.
onreadystatechange method:
function zipWeather() {
if (window.XMLHttpRequest) {
var xhr = new XMLHttpRequest();
}
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
//responseText code
}
// else ifs for readystate 3 2 and 1 gives a console log
else {
console.log("request failed, retrying in 5 seconds...");
window.setTimeout(zipWeather, 5000);
return;
}
}
xhr.open("GET", "http://api.openweathermap.org/data/2.5/weather?q=" + document.getElementById("zipweather").value,
true);
xhr.send();
event listeners instead of onreadystatechange:
xhr.addEventListener("load", function() {
//responseText code
}, false)
xhr.addEventListener("error", function(err) {
console.log(err);
if (weatherData.retryCount <= weatherData.retryMax) {
weatherData.retryCount++;
console.log(weatherData.retryCount);
console.log(err);
zipWeather();
return;
}
else {
return;
}
and of course jquery:
function zipWeather() {
$.ajax({
type: 'GET',
url: 'http://api.openweathermap.org/data/2.5/weather?q=' + $("#zipweather").val(),
data: {},
dataType: 'json',
success: function(data) {
console.log(data.name);
$('#weather').html(data.name).css({'color': 'blue', 'text-shadow': '1px 0.5px lightblue'});
//change from kelvin
var localTempF = Math.round((data.main.temp - 273.15) * 9/5 + 32);
$('#localtemp').html(localTempF + weatherData.localUnits);
weatherData.localTemperatureValue = localTempF;
},
timeout: 3000,
retryCount: 0,
retryMax: 5,
error: function(jqXHR, textStatus, errorThrown) {
this.retryCount++;
if (this.retryCount <= this.retryMax) {
//try again
$.ajax(this);
return;
}
return;
}
The last two methods use a retryCount and retryMax variable trick I found in What's the best way to retry an AJAX request on failure using jQuery? so it doesn't keep calling the API if its down.
Finally, questions:
Are all of these methods virtually identical in terms of performance? Is there a potential catastrophic bug lurking in any method as written?
Is it most proper to use a callback in the error handler to the same function when using AJAX?
Are javascript code standards moving away from using onreadystatechange or event handlers and towards jquery $.ajax and $.get functions?
Thanks everyone. Sorry it was so long!
You can probably answer the performance question yourself with performance testing tools. I prefer event listeners because the code reads more cleanly. Bug-wise, I would categorize the lack of a way for the first method to break out of a callback loop if the service is down as a serious bug. It could cause performance degradation if the service is down, that is something to check with performance testing.
I don't know whether there is a convention to re-call the method in the error handler, but it seems like an OK way to handle it as long as you have a loop breakout. If the retry max is reached, you might want to alert the user and prompt them that the service call will be attempted again after some arbitrary period. See the way Gmail handles connection interruptions for an example of this behavior.
As for jQuery v. not jQuery, it depends on your use case. In a lightweight web page where you are performing minimal JavaScript coding, you might find jQuery to be overkill in terms of the size of the library. On the other hand, jQuery is popular because it papers over browser incompatibilities, letting you, the coder, focus on application functionality. If you load it from a popular CDN, many users may already have it in cache, so load time would not be a factor. As for what people use, jQuery is popular, but beyond that I don't know whether any statistics exist that break down the relative popularity of either method.
Answers to your three questions:
1. Performance
Since the way to send the request in JavaScript doesn't affect the actual network loading performance at all, it simply doesn't matter. Also, there's almost no client side performance difference between the three.
2. Callback when failing
You handle that actually very nicely and elegant. Don't worry about a method unless its slow or doesn't work :D
3. Which one?
That's totally up to you! If you want to do jQuery, do it. If you feel like doing plain JavaScript with event listeners, do that.
Hope it helps, if you have any questions, feel free to ask :)

How to work with 2 XMLHttpRequest one dependent on another?

I am working on a project where I have got 2 XMLHttpRequest() objects, say A and B.
What I want to accomplish is when A finish fetching a list of data items, B will be triggered to fetch some more items based on the previous data items fetch by A.
Currently my problem is that the two objects are working independent of one another.
My code is below:
var A = new XMLHttpRequest();
var B = new XMLHttpRequest();
A.open("GET", directory, true);
A.onreadystatechange = function () {
if (A.readyState === 4) {
if (A.status === 200 || A.status == 0) {
//does... something
}
}
}
A.send(null);
while(true){
B.open("GET", another_directory, false);
B.overrideMimeType("application/document");
B.send(null);
if (B.status == "404")
continue;
//does... something else
}
This code is not working because I find evertime B proceed before A can complete. I basically don't know which event to use.
How can I accomplish my objective?
What events can I use so that I can sync processing B right after finishing with A?
Ok, so let's start with your code. I've added a few comments to it, so now you can understand the source of the problem:
var A = new XMLHttpRequest(); //You create an XMLHttpRequest object
var B = new XMLHttpRequest(); //And an another
A.open("GET", directory, true);
/* Now you open a GET request to DIRECTORY, with async TRUE. The third parameter can
make a request sync or async, but sync is not recommended as described below. */
A.onreadystatechange = function () {
if (A.readyState === 4) {
if (A.status === 200 || A.status == 0) {
/* So you registered an event listener. It runs when the readyState changes.
You can use it to detect if the request is finished or not. If the readyState is
4, then the request is finished, if the status code is 200, then the response is
OK. Here you can do everythin you want after the request. */
}
}
}
A.send(null); //Now you send the request. When it finishes, the event handler will
// do the processing, but the execution won't stop here, it immediately goes to the
// next function
while(true){ // Infinite loop
B.open("GET", another_directory, false); //Open request B to ANOTHER_DIRECTORY,
// but now, request B will be synchronous
B.overrideMimeType("application/document"); // Configure mime type
B.send(null); // Send the request
if (B.status == "404")
continue;
// If it's not found, then go to the next iteration
// and do something else
}
I hope that now you can see the source of the problem. When you run this script, then your start an async request and then immediately start the next one. Now you can choose from 2 ways.
Run next request from callback (recommended)
It's the better way. So start your first (async) request and in the event listener (where you do the processing) you can start the next request. I've made a commented example here: http://jsfiddle.net/5pt6j1mo/1/
(You can do it without arrays - it was just an example)
If you use this way then the GUI won't freeze until you are waiting for response. Everything will be responsible so you can interact with the page, you can create cancel button, etc.
Synchronous AJAX (not recommended)
I don't recommend it because "Synchronous XMLHttpRequest on the main thread is deprecated" in Chrome, but if you really want to then you can try to use this solution. So an XMLHttpRequest's open function has 3 arguments:
METHOD: which HTTP methid to use
URL: which URL to request
ASYNC: Asynchronous request? If false then it will be synchronous wich means that after you call .send(), it will pause execution until the response comes back.
So if you set the third parameter to FALSE then you can easily do it... but you shouldn't!
Here is an alternative solution, either use the fetch API or promisify native XHR and this problem becomes much simpler:
fetch(directory).then(function(response){
// some processing
return fetch(another_directory); // can change content type too, see the mdn docs
}).then(function(responseTwo){
// all processing is done
}).catch(function(err){
// handle errors from all the steps above at once
});
This is just as native as XHR, and is much much simpler to manage with promises.
(After lengthy edit) I'd recommend strongly that you take the time to understand the nature of asynchronous calls within JavaScript. Here's a bit of recommended reading.Asynchronous Programming in JavaScript I think that is simple enough to understand what is going on. Note: Stop reading at "Enter Mobl".
In JavaScript when you call a function, the system places that function into a 'queue' with an implicit instruction to go ahead and run it as soon as you can. It does that for each and every function call. In your case you are telling the system to run A, then run B. A goes in the queue, B goes in the queue. They are submitted as individual functions. B happens to run first.
For normal functions, if you want to control the sequence, you can nest the A function call within the B function call. But oops. You are using XMLHttpRequest, so that limits your ability to customize the functions. Read on. Check out Ajax Patterns on the subject Look at the paragraph for "Asynchronous Calls". Look at your code...
A.onreadystatechange = function () {
if (A.readyState === 4) {
if (A.status === 200 || A.status == 0) {
//does... something
(RUN ALL THE B.methods right here...)
}
}
}
I think that will get you to your destination, assuming you want a no jQuery solution.
For the person who just wants a functioning system, and doesn't want to understand the language better, here is a jquery solution... Note how the B function call is nested within the A function call. Do note that the order of this nesting is based on the presence of the jQuery success tag. If not using jQuery, you will manually have to nest the functions as appropriate.
var special_value;
$("button").click(function(){
$.ajax({url: "demo_testA.html",
type: 'GET',
success: function(resultA){
special_value = resultA;
$.ajax({url: "demo_testB.html",
type: 'GET',
data: special_value,
success: function(resultB){
$("#div1").html(resultB);
}});
});
});
I will say, it would be much easier to help you help yourself with the use of better communications. If you don't like something, then so state. If you don't understand something ask for more clarification or edit your problem statement. Feedback is a good thing.

Can this ever be a race condition? (JavaScript)

I'm looking for a solid answer on whether the following JavaScript code contains a race condition or not.
The question boils down to this: If I begin listening for the completion of an asynchronous task (such as an AJAX call) immediately after I've initiated the task, could that task complete before I've started listening?
I've found a few similar questions, but none has an answer that seems totally concrete ("there could be a problem ... it is unlikely that..."). Here's a rough example of the kind of situation I'm referring to:
// Publish an event synchronously
function emit(key){}
// Subscribe to an event
function on(key, cb){}
// Request the given url;
// emit 'loaded' when done
function get(url) {
http.get(url, function() {
emit('loaded');
});
}
get(url);
on('loaded', function() {
// Assuming this subscription happens
// within the same execution flow as
// the call to `get()`, could 'loaded'
// ever fire beforehand?
});
Even better if the answer has backing from the actual language specification (or another definitive source)!
No, there can be no race condition.
The asynchronous task could complete before you start listening to the event, but that doesn't matter. The completion of the task creates an event, and that event won't be handled until the function (or code block) ends and the control is returned to the browser.
#Guffa is correct. But, there are at least two situations where you can have the appearance of a race condition.
Maybe there is an error during the ajax request that isn't handled. Consider some typical XMLHttpRequest code:
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200) {
call_success_handler();
}
else {
call_error_handler();
}
}
};
request.open("GET", url , true);
request.send(null);
If the readyState is never '4', then no handlers will be called and no errors will be reported. Your success handler is never triggered, so you assume that the event fired too fast and you didn't notice.
It's less common now, but there are also cases where browsers may make you think you have a race condition. The specification says what is supposed to happen in error conditions, but it wasn't always that way. Old / non-conforming XMLHttpRequest implementations behave poorly with oddball network conditions. The initial (ca. 2006) versions of the spec didn't even address network level errors.
Hopefully most browsers have conforming implementations now, and hopefully most frameworks should handle error conditions properly.
There's a great article by Pearl Chen on asynchronous debugging that's worth a read if you want to dig into it deeper.
Also, there's more information on ajax problems here: jQuery $.ajax, error handler doesn't work.

IE 9 Javascript error c00c023f

I came across this error only on IE9:
SCRIPT575: Could not complete the operation due to error c00c023f.
The error happened on this line: if ((a.responseXML) && (a.readyState==4)) {
I cant figure it out why this happened, and it seems to work very well in other browsers.
and this is my javascript code:
var a = new XMLHttpRequest();
a.open("GET",'/cust/ajax/getresult.php?qk=nnf87&arg1='+pzid,true);
a.onreadystatechange = function () {
if ((a.responseXML) && (a.readyState==4)) {
var N = a.responseXML.getElementsByTagName('result')
sequence = N[0].firstChild.data;
var SEQ = sequence.split(",");
var num = SEQ.length;
var sum = 0;
for(var n=0;n<num;n++){sum = sum + (SEQ[n]*1);}
//document.getElementById("the_number_of").innerHTML = sum;
var date = new Date();
date.setTime(date.getTime()+(2*60*60*1000));
document.cookie='cpa_num='+sum+'; expires= '+date.toGMTString()+'; path=/';
}
}
I don't suppose your request is being aborted? A quick Googling found this blog post. It would seem that an aborted request in IE9 will give this error when trying to read any properties off of the XMLHttpRequest object.
From the post, their particular problem with this error code could be duplicated by:
Create a XMLHttpRequest object
Assign an onreadystatechanged event handler
Execute a request
Abort the request before the response has been handled
You will now see that the readystatechange handler will be called,
with the readystate property set to '4'. Any attempt to read the
XmlHttpRequest object properties will fail.
The author mitigates this problem by assigning an abort state to the request when the manual-abort is performed, and detecting it and returning before trying to read any other properties. Though this approach would only really work if you are performing the abort yourself.
A similar problem was documented on the this WebSync Google Groups post. Towards the end of the discussion there is an implication that this problem only occurs
if you've got the standards and IE9 rendering
modes both set
Hope that points you in the right direction.
Within the readyState==4 routine, include a try and catch similar to:
try {
var response=xmlHttp.responseText;
}
catch(e) {
var response="Aborted";
}
We found that this to be the most successful resolution to the above.
Switch the
if ((a.responseXML) && (a.readyState==4))
to
if ((a.readyState==4) && (a.responseXML))
As the order matters. it seems that on IE9 if the state is not 4, the responseXML and reponseText yield this error if being accessed (I have no clue why...)
I was getting this error in my Framework. It only shows up in IE (go figure). I simply wrapped the response like below:
if(request.readyState == 4)
{
// get response
var response = request.responseText;
}
It happens for me with IE9 when I read the "status" property prematurely (before readyState is 4 / DONE).

Categories