How can I simulate an XMLHttp synchronous request with an asynchronous one.
We have this legacy web application that populates the DOM from an ActiveX XMLHttp request. It was originally written around the turn of the century and it's showing it's age. If I can get through this I'll (hopefully) have the chance to rewrite this so that it's a little more manageable.
We're running into problems when we use IE9 to access the SSL version of our site. All of our requests are synchronous but in IE9 we’re getting errors saying that a particular piece of data doesn’t exist when we subsequently access the DOM. It appears that it’s continuing to execute code before the DOM is finished loading. Our interim solution was to introduce a 10 second delay before accessing the request. It’s a hack but for the most part it seems to work.
I’d like to use the readystate to figure out if it’s finished loading which means moving the request from synchronous to asynchronous execution. I’ve searched through the forums but I haven’t found an example of this yet. That being said I'm just trying to figure out when it's finished loading.
Does anyone know of a method to simulate a synchronous XMLHttp request using an asynchronous request? The people here have been trying to fix this issue for years with poor results.
Best practice is to split your method into two around the call. So, where you may have:
PHP Code:
var doStuff = function() {
// do some stuff
// the asynchronous call you wish to treat "synchronously"
var result = post(url, params);
// do some more stuff
} // doStuff()
You'd rewrite like so:
PHP Code:
var doStuff = function() {
// do some stuff
// the third parameter is a callback
var result = post(url, params, function() { doStuffContinue.apply(this, arguments) });
} // doStuff()
var doStuffContinue = function() {
// do some more stuff
} // doStuff()
Though, because of the nature of this, you may end up having to write it like so:
PHP Code:
var doStuff = function() {
// do some stuff
// the third parameter is a callback
var that = this;
var result = post(url, params, function() { doStuffContinue.apply(that, arguments) });
} // doStuff()
var doStuffContinue = function() {
// do some more stuff
} // doStuff()
Thanks for your reply. I'm a little confused though. I didn't think JavaScript had a straight 'post' command. Here's (roughly) what's happening now:
Code:
// method = 'POST', async now false but I want to make it true
var myFunction = function (method, url, async, uname, upass, content)
{
var http = new ActiveXObject("msxml2.xmlhttp" + XMLLIB_VERSION);
http.open(method, url, obj.m_async, obj.m_uname, obj.m_upass);
http.send(content);
if (http.statusText == 200)
{...}
}
I thought I would have to have created a function to attach to the onreadystatechange of the XMLHttp request. I'm using straight JavaScript. (Well technically JScript I guess since it's IE only.)
Right. post() is a standing for whatever you're doing to assemble and make your request. So, that does means you have to pass the callback method down the chain. And, in my example, I'm just assuming the post() method returns the request itself, and the remaining portion of the code operates on the completed request object, accessing the responseText, responseXML, or response headers as necessary.
So, you might implement post() like this:
PHP Code:
var post = function (url, data, callback, user, pass) {
var request = getXMLHttpRequest();
request.onreadystatechange = function() {
if (request.readystate == 4) {
callback();
}
}
request.open("POST",url,true,user,pass);
request.send(data);
return request;
} // post()
var getXMLHttpRequest = function () {
if (window['XMLHttpRequest']) {
return new XMLHttpRequest();
} else {
return new ActiveXObject("Microsoft.XMLHTTP");
}
} //getXMLHttpRequest
Or something like that ... You'll have to test and tweak it yourself, of course. But, I think this is the concept you're looking for. Let me know if I've misunderstood, of course.
This looks to be almost what I need. The function I'm using though is in the middle of a stack of other functions so although it does execute the doStuffContinue after it gets it the script keeps running.
In order for this to work there can't be any code that goes after the post can there?
j
p.s. The xml coming back is not a page per se. Just data that will be used to fill the page.
Last edited by Jason Cameron; 05-07-2012 at 06:50 AM.
here is a magic jabvascript function that turns async code into sync-style code.
i threw a demo in too, so you know how to actually use it.
Code:
function Sentry(){
var args=arguments, ar=[].slice.call(args[0]);
var caller=args.callee.caller;
var str=caller.toString();
var pc=str.split(/\bendSentry\b/)[0].split(/\bnew Sentry\b/)[1].split(/\bcontinueSentry\b/);
var tail="function tail(){"+caller.toString().split("endSentry;")[1].trim();//
var left=pc.length;
var head=str.split(/return\s+new\s+Sentry/)[0]+";var _left="+left+";";
head=head.replace(/function\s+([\w_$]+)/g,"function _sentry_$1");
var fnName=head.split("(")[0].split(/function\s+/)[1];
var lut={}, wr;
if((wr=str.split(/\bendSentry\b/)).length>2){
throw new TypeError( "Sentry: duplicate 'endSentry' keyword detected in "+caller.name+"() at line #"+ (wr.slice(0,2).join(" ").split("\n").length).toString());
}
var pc2=str.toString().replace("new Sentry(arguments)","new Sentry").split(/\bendSentry\b/)[0].split(/\bnew Sentry\b/)[1].split(/\bcontinueSentry/);
var myCBs=pc.map(function(a,b,c){
var varName=a.split(/Sentry\(([^\)]+)\)/)[1];
var cmd=varName;
var newCB=String("function _sentry_"+varName+"(value){"+varName+"=value; "+
"var sentryOb="+fnName+".sentry.callBacks['"+varName+"']; sentryOb.expired=true;sentryOb.runtime=(sentryOb.finished=+new Date)-sentryOb.born; if(!--_left){tail();}}");
var ret= {name: varName, cb: newCB, born: +new Date, expired: false};
return lut[varName]=ret;
});
var bod=pc2.map(function(a){return a.replace(/Sentry\(([\w_$]+)\)/g, function(j,nam){ return lut[nam].cb; }); }).join("\n");
var myCode=[head, bod,tail,"}"].join("\n");
var myFN=eval("0||"+myCode);
var out={complete:tail, callBacks: lut, count: left, caller: caller.name+"("+ar+")", born:+new Date };
myFN.sentry=out;
myFN.apply();
return out;
}//end Sentry()
Demo usage:
Code:
//normal async ajax function:
function aGet(turl, callback) {
var XHRt=new XMLHttpRequest;
XHRt.onreadystatechange = function () {if (XHRt.readyState == 4 && XHRt.status == 200) {callback(XHRt.responseText, XHRt);}};
XHRt.open("GET", turl, true);
XHRt.send("");
return XHRt;
};window.aGet=aGet;
// helper function: plucks the title from an html page's string source:
function getTitle(strHTML){
return strHTML.split("</title>")[0].split("<title>").slice(-1)[0];
}
//demo of sync syntax in a magic ajax function:
function getSomePages(pageURL){
var page, site; //defines placeholders for async operation returns
//install sentry by returning an invocation BEFORE you do anything else:
return new Sentry(arguments);
//fetch the two resources using async ajax, building a memorization callback using Sentry(varName) syntax.
aGet(".", Sentry(page) );
continueSentry; //this keyword goes between request #1 and request # (total-1)
aGet("/", Sentry(site) );
endSentry; //this keyword goes after all magic dispatches to sentry
//use the async-defined vars as though you know them already:
alert( getTitle(page)+" |||| "+getTitle(site) );
}
getSomePages(".")
without comments, you can see how clean the former async code turns into sync-like procedural style simpleness:
Code:
function getSomePages(pageURL){
var page, site;
return new Sentry(arguments);
aGet(".", Sentry(page) );
continueSentry;
aGet("/", Sentry(site) );
endSentry;
alert( getTitle(page)+" |||| "+getTitle(site) );
}
Correct. Using the "best-practice" approach, anything and everything that needs to be executed after the request needs to be executed with a callback. With that in mind, you just need to treat the function containing post() as an asynchronous function and repeat the pattern in the container:
PHP Code:
function a() { // do stuff var that = this; b(p1, p2, function() { a_continue.apply(that); }); }
function a_continue() { // do more stuff }
function b(p1, p2, callback) { // do stuff var that = this; post(url, params, function() { b_continue.apply(that); }); }
function b_continue() { // do more stuff. execute callback, if necessary. if (callback && typeof(callback) == 'function') { callback(); } }
And of course, anything containing the container needs to work similarly.
Bookmarks