Synchronous AJAX call not truly synchronous - javascript

I'm having trouble with a synchronous AJAX request.
I am creating a web application where there are many AJAX requests called in sequence which should be returned in strict order. Rather than putting every succeeding request in the readystatechange event handler of the last request, I decided to simply call them synchronously.
However, in the following code, alert() is invoked before adding the response to the DOM.
window.addEventListener("load", main, false);
function main (e) {
// Sending synchronous request
var request = new XMLHttpRequest();
request.open("GET", fileName, false);
request.overrideMimeType("text/xml");
request.send(null);
// Receiving response
var response = request.responseXML;
// Changing XML Tree to corresponding XHTML Tree
response = XMLtoXHTML(response);
//Adding response to the body
document.body.appendChild(response);
// Calling alert
alert("Hello World");
}
The response gets in fact successfully added to the DOM, but only after clicking OK on the alert message. When I do a walkthrough using Safari's Script Debugging features, the response does get added to the DOM before calling alert().
Any suggestions?
NB: I left out other requests.

I guess it actually does get added, you just don't see the changes before JS returns control back to the browser, i.e. until alert window is closed. If you cannot do an async request (which I would suggest first), you could at least make dom changes in a separate setTimeouted "thread".

It has indeed been added to the DOM but the page hasn't parsed the new element into view yet. If I had to guess it would be that since alert blocks the current running JS thread webkit tries to perform reflows during this time for efficiency.
Test case:
function foo()
{
var div = document.createElement("div");
div.innerHTML = "hi friends";
div.id = "bar";
document.body.appendChild(div);
alert(document.getElementById("bar").innerHTML);
}
Do you need the element to appear before the alert comes up? Use this in that case
setTimeout(function(){alert("Hello World");}, 1);

Or use a flow control library like Frame.js (or asnyc, flow.js, more):
Frame(function(next)){
document.body.appendChild(response);
next();
});
Frame(function(next)){
alert("Hello World");
next();
});
Frame.init();

Related

Why should we put onload before send method

I am new to JavaScript and would like to ask about about AJAX, that is, why we put xhr.onload before xhr.send() since even if I put xhr.onload after xhr.send() all works perfectly. But majority of tutorials teach you to put onload before send() without proper explanation. So, should I use
let btn = document.querySelector('button').addEventListener('click', function(){
let xhr = new XMLHttpRequest();
xhr.onload=function(){
if(this.status===200){
let div=document.querySelector('div').innerHTML=xhr.responseText;
}
}
xhr.open('GET', './mir.txt');
xhr.send();
})
Or
let btn = document.querySelector('button').addEventListener('click', function(){
let xhr = new XMLHttpRequest();
xhr.open('GET', './mir.txt');
xhr.send();
xhr.onload=function(){
if(this.status===200){
let div=document.querySelector('div').innerHTML=xhr.responseText;
}
}
})
and WHY?
Use your first version.
Logically, in the situation where the browser has the response cached, then XHR could complete instantly, and then you try to add "onload" after the response has already loaded, then nothing will happen.
In reality, even when cached, I don't think this can happen because of how the browser engine works, but from the coding point of view, it looks like it could happen. So making the pattern have onload at the top removes all suspicion that such behaviour could occur. Possibly in older browsers, when people did tend to do XHR reuests manually, that kind of thing was an actual danger?
I do know, in the scenario where you syncronously load a request, it does matter, because the thread will be blocked (as well as the whole window) during the send until it completes.
Onload is most frequently used within the element to perform a script once a website has fully loaded (including script files, images, CSS files, etc.) So Its a good approach lets load all the dependency after that we make a API call or Ajax call to update our DOM.
You don't have to use onload() before send(), your first and second example shows that already.
onload() is an event of XHR Object (a property for the load event of XHR), so it'll execute automatically when a particular event satisfies during the XHR execution. The function called when an XMLHttpRequest transaction completes successfully. So, Using onload() property you just define/tell what needs to do when load event satisfied. You don't have to define if you don't need it.
send() is a method of XHR, not an event. so you need to call it if you want it. see reference link to see more about its behavior for synchronous and asynchronous call.
Ref:
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestEventTarget/onload

Using script tag to pass arguments to JavaScript

I need to implement a cross-site comet http server push mechanism using script tag long polling. (phew...) For this, I dynamically insert script tags into the DOM and the server sends back short js scripts that simply call a local callback function that processes the incoming messages. I am trying to figure out a way to associate each one of these callback calls with the script tag that sent it, to match incoming replies with their corresponding requests.
Clearly, I could simply include a request ID in the GET url, which is then returned back in the js script that the server generates, but this creates a bunch of unnecessary traffic and doesn't strike me as particularly elegant or clever.
What I would like to do is to somehow associate the request ID with the script tag that I generate and then read out this request ID from within the callback function that is called from inside this script tag. That way, all the request management would remain on the client.
This leads me to the following question: Is there a way to ask the browser for the DOM element of the currently executing script tag, so I can use the tag element to pass arguments to the contained javascript?
I found this thread:
Getting the currently executing, dynamically appended, script tag
Which is asking exactly this question, but the accepted answer isn't useful to me since it still requires bloat in the server-returned js script (setting marker-variables inside the script) and it relies on unique filenames for the scripts, which I don't have.
Also, this thread is related:
How may I reference the script tag that loaded the currently-executing script?
And, among other things, suggests to simply grab the last script in the DOM, as they are executed in order. But this seems to only work while the page is loading and not in a scenario where scripts are added dynamically and may complete loading in an order that is independent of their insertion.
Any thoughts?
PS: I am looking for a client-only solution, i.e. no request IDs or unique callback function names or other non-payload data that needs to get sent to and handled by the server. I would like for the server to (theoretically) be able to return two 100% identical scripts and the client still being able to associate them correctly.
I know you would like to avoid discussions about changing the approach, but that's really what you need to do.
First, each of the script tags being added to the DOM to fire off the poll request is disposable, i.e. each needs to be removed from the DOM as soon as its purpose has been served. Else you end up flooding your client DOM with hundreds or more dead script tags.
A good comparable example of how this works is jsonp implementations. You create a client-side named function, create your script tag to make the remote request, and pass the function name in the request. The response script wraps the json object in a function call with the name, which then executes the function on return and passes the json payload into your function. After execution, the client-side function is then deleted. jQuery does this by creating randomly generated names (they exist in the global context, which is really the only way this process works), and then deletes the callback function when its done.
In regards to long polling, its a very similar process. Inherently, there is no need for the response function call to know, nor care, about what script tag initiated it.
Lets look at an example script:
window.callback = function(obj){
console.log(obj);
}
setInterval(function(){
var remote = document.createElement('script');
remote.src = 'http://jsonip.com/callback';
remote.addEventListener('load', function(){
remote.parentNode.removeChild(remote);
},false);
document.querySelector('head').appendChild(remote);
}, 2000);​
This script keeps no references to the script elements because again, they are disposable. As soon as their jobs are done, they are summarily shot.
The example can be slightly modified to not use a setInterval, in which case you would replace setInterval with a named function and add logic into the remote load event to trigger the function when the load event completes. That way, the timing between script tag events depends on the response time of your server and is much closer to the actual long polling process.
You can extend this even further by using a queueing system to manage your callbacks. This could be useful if you have different functions to respond to different kinds of data coming back.
Alternatively, and probably better, is to have login in your callback function that handles the data returned from each poll and executes whatever other specific client-side logic at that point. This also means you only need 1 callback function and can get away from creating randomly generated callback names.
If you need more assistance with this, leave a comment with any specific questions and I can go into more detail.
It's most definitely possible but you need a little trick. It's a common technique known as JSONP.
In JavaScript:
var get_a_unique_name = (function () {
var counter = 0;
return function () {
counter += 1;
return "function_" + counter;
}
}()); // no magic, just a closure
var script = document.createElement("script");
var callback_name = get_a_unique_name();
script.src = "/request.php?id=12345&callback_name=" + callback_name;
// register the callback function globally
window[callback_name] = function (the_data) {
console.log(the_data);
handle_data(the_data); // implement this function
};
// add the script
document.head.appendChild(script);
The serverside you can have:
$callback_name = $_GET["callback_name"];
$the_data = handle_request($_GET["id"]); // implement handle_request
echo $callback_name . "(" . json_encode($the_data) . ");";
exit; // done
The script that is returened by /request.php?id=12345&callback_name=XXX will look something like this:
function_0({ "hello": "world", "foo" : "bar" });
There may be a solution using onload/onreadystate events on the script. I can pass these events a closure function that carries my request ID. Then, the callback function doesn't handle the server reply immediately but instead stores it in a global variable. The onload/onreadystate handler then picks up the last stored reply and tags it with the request ID it knows and then processes the reply.
For this to work, I need to be able to rely on the order of events. If onload is always executed right after the corresponding script tag finishes execution, this will work beautifully. But, if I have two tags loading simultaneously and they return at the same time and there is a chance that the browser will execute both and afterwards execute botth onload/onreadystate events, then I will loose one reply this way.
Does anyone have any insight on this?
.
Here's some code to demonstrate this:
function loadScript(url, requestID) {
var script = document.createElement('script');
script.setAttribute("src", url);
script.setAttribute("type", "text/javascript");
script.setAttribute("language", "javascript");
script.onerror = script.onload = function() {
script.onerror = script.onload = script.onreadystatechange = function () {}
document.body.removeChild(script);
completeRequest(requestID);
}
script.onreadystatechange = function () {
if (script.readyState == 'loaded' || script.readyState == 'complete') {
script.onerror = script.onload = script.onreadystatechange = function () {}
document.body.removeChild(script);
completeRequest(requestID);
}
}
document.body.appendChild(script);
}
var lastReply;
function myCallback(reply) {
lastReply = reply;
}
function completeRequest(requestID) {
processReply(requestID, lastReply);
}
function processReply(requestID, reply) {
// Do something
}
Now, the server simply returns scripts of the form
myCallback(message);
and doesn't need to worry at all about request IDs and such and can always use the same callback function.
The question is: If I have two scripts returning "simultaneously" is it possible that this leads to the following calling order:
myCallback(message1);
myCallback(message2);
completeRequest(requestID1);
completeRequest(requestID2);
If so, I would loose the actual reply to request 1 and wrongly associate the reply to request 2 with request 1.
It should be quite simple. There is only one script element for each server "connection", and it can easily be stored in a scoped, static variable.
function connect(nameOfCallback, eventCallback) {
var script;
window[nameOfCallback] = function() { // this is what the response invokes
reload();
eventCallback.call(null, arguments);
};
reload();
function reload() {
if (script && script.parentNode)
script.parentNode.removeChild(script);
script = document.createElement(script);
script.src = "…";
script.type = "text/javascript";
document.head.appendChild(script);
// you might use additional error handling, e.g. something like
// script.onerror = reload;
// but I guess you get the concept
}
}

getElementById() null error

For those who wants the soluce before the question :
1 ) Don't read an element with getElementById() before it's really created : see windows.onload.
2 ) If you are using XMLHTTPRequest and AJAX stuff, call you're getElementById() , or unlock this function, in the callback ,(xhr_object.readyState == 4 part of your request).
In my case, i call my page without using the desired callback (noobish Ctrl-C Ctrl-V style).
Here was the question :
I'm facing a strange case in a HTML/Javascript code.
The aim of this code is to get the value of an HTML input (type text) via a JS function.
The context is that a main HTML page loads my all my JS functions, and loads on demand HTML contents in sub divs via HTTPRequest.
The selected code is called after the divs has been loaded.
Here's the PHP generated Input field that i have to read:
<input id="listejf" type="text" value="6|7|">
Here's my JavaScript call :
listejf=document.getElementById('listejf').value;
alert(listejf);
This code doesn't work. Firebug sends me:
TypeError: document.getElementById("listejf") is null
The strange thing is that I can make it work if I call the getElementById through an alert like this:
alert(document.getElementById("listejf"));
listejf=document.getElementById('listejf').value;
alert(listejf);
The first alert displays null, but the second one "6|7|", as expected.
Now, 2 questions:
Why does alert make it work ?
How can I make it work without throwing alerts everywhere?
Re-edit, the code was gone :
That's is the main HTML page : main.html
<head>
<script type="application/javascript" src="./preload.js"></script>
</head>
<body>
Link
<div id="targetid"></div>
</body>
preload.js looks like that :
function CallPagen() {
envoieRequete('./PageN.php', 'targetid');
}
function mytestfunction() {
listejf = document.getElementById('listejf').value;
alert(listejf);
}
function envoieRequete(url, id) {
var xhr_object = null;
if (window.XMLHttpRequest) xhr_object = new XMLHttpRequest();
else if (window.ActiveXObject) xhr_object = new ActiveXObject("Microsoft.XMLHTTP");
xhr_object.open("GET", url, true);
xhr_object.onreadystatechange = function () {
if (xhr_object.readyState == 4) {
if (!document.getElementById(id)) {
alert("id pas trouvé " + id);
}
document.getElementById(id).innerHTML = xhr_object.responseText;
mytestfunction();
}
};
xhr_object.send(null);
}
PageN.php just echoes the inputtext field with the value filled.
"...I can make it work if i call the getelement through an alert like this..."
This almost always means that you're making an asynchronous AJAX request.
"Why does alert make it work ?"
What happens is that the alert delays the processing of the next line of code long enough for the response to return.
Without the alert, the next line of code runs immediately, and the element is not yet available.
"How can I make it work without throwing alerts everywhere?"
This is a very common issue. The solution is that you need to put any code that relies on the response of the XMLHttpRequest request inside a callback to the request.
So if you're making a request through the native API, you'd add a onreadystatechange callback...
xhrRequest.onreadystatechange = function() {
if (xhrRequest.readyState === 4) {
// append the response text to the DOM
listejf=document.getElementById('listejf').value;
alert(listejf);
}
}
You should run the code after DOM is ready, when the alert() is called, document is loading and browser has time for creating the DOM objects, try the following:
The load event fires at the end of the document loading process. At this point, all of the objects in the document are in the DOM, and all the images and sub-frames have finished loading.
window.onload = function() {
var listejf = document.getElementById('listejf').value;
alert(listejf);
};

How to invoke a "Please Wait" window only if ajax takes more than X milliseconds to respond?

I'm doing an AJAX call (regular JS) and, if it takes more than, say, 500 milliseconds, I'd like to put up my "Please Wait" box.
Normally, if I want to put up the PW box immediately, I'd do:
// show semi-transparent grey screen to block access to everything underneath
divGreyCoverAllNode.style.display = 'inline';
// show PW box. Prior to these lines, both coverall and PW were display=none
divPleaseWaitNode.style.display = 'inline';
// now do the AJAX and follow-up inside a zero timer; the timer is necessary to
// make the system pause to display the screen changes we previously invoked
setTimeout( function() {
// do my ajax call here, then after the call...
// take down the PW stuff
divPleaseWaitNode.style.display = 'none';
divGreyCoverAllNode.style.display = 'none';
},
0
);
Like I stated above, what I'd like to do is have the PW displayed only if AJAX doesn't finish in, say, 500 milliseconds. Ideally it would be something like:
// set a timer to display PW in 500 milliseconds
myTimeEvent = setTimeout( function() {
divGreyCoverAllNode.style.display = 'inline';
divPleaseWaitNode.style.display = 'inline';
},
500
);
// do my ajax call here, then after the call...
clearTimeout(myTimeEvent);
// take down the PW stuff, in case it was displayed
divPleaseWaitNode.style.display = 'none';
divGreyCoverAllNode.style.display = 'none';
But I can't seem to get the system to pause and display the PW when AJAX is taking its time. I've tried surrounding the AJAX-and-follow-up block in a zero timer, but no deal.
Any suggestions?
EDIT:
Important fact: This is not an asynch ajax call. It's an unusual situation that requires everything to wait on the ajax result.
Given that you are making a synchronous XHR call, you can't. That's the nature of synchronous – everything stops until the call completes. When you use a synchronous XHR request, not only is the JavaScript event loop stopped, you actually freeze the entire browser UI (in IE and Firefox < 3).
That said, you're doing it wrong. 8.4% of reported IE9 hangs last month were due to synchronous XHR. There really is no such thing as ‘an unusual situation that requires use of synchronous XHR requests.’ Make your request, then act on the data you get in the callback function.
Instead of something like:
// Do stuff, then when you need a request:
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send();
// Do more stuff
alert(xhr.responseText);
You need:
// AJAX helper
function req(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) callback(xhr);
}
}
// Your code. Do stuff, then when you need an AJAX request:
req(url, function(xhr) {
// Do more stuff
alert(xhr.responseText);
});
Obviously this needs refined, but this illustrates the proper way to make an AJAX request.
It shouldn't come after the ajax call, it should come inside the callback function. AJAX requests are asynchronous with the rest of the code, you should preform actions you want upon completion inside the callback part of your request.
take a look at BlockUi. If that doesn't look like it will work for you, you could try using
$(document).ajaxStop(DoSomething());
Take a Look at jQuery Timeout

Javascript asynchronous calls stepping on one another

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.

Categories

Resources