David Mark's Daily Javascript Tips - Volume #3 - Tip #10 - How toCreate an XHR (Ajax) Object

D

David Mark

How to Create an XHR (Ajax) Object


var xhrCreated;

// As always, shorthand feature detection for example--use
isHostMethod in this case

// Degrades in IE 6- :)

if (this.XMLHttpRequest)

// Use try-catch as this host object may explode on creation

try {
var x = new XMLHttpRequest();
xhrCreated = true;
} catch(e) {
}
}

if (xhrCreated) {
var createXhrObject = function() {
return new XMLHttpRequest();
};
}

if (createXhrObject) {

// Put app that must create XHR objects here

}


Thanks to IE, you can't assume that file protocol will work. Put
something like this at the top of your xhrSend function:-

if (uri.slice(0, 5) == 'file:') {
throw new Error('No file prototcol requests with this rendition!');
}

....and take it out on deployment. Pattern of builder should be clear
at this point: checkboxes for degradation points and other questions
of context, plus a checkbox for "debug version", which includes lots
of additional throws to keep the developers honest. It should be
obvious that the build flavor must be settled on at *design* time, not
at deployment. All you do at deployment is uncheck "debug version"
and get the same exact code you developed with, minus the cattle
prods. ;)

Contrast this with breaking up all of your supporting code into tiny
bits, loading them dynamically with script and dealing with all of the
related complications, then running all of these bits through a
"compiler" to piece together something that might approximate what you
encountered during development. Doesn't matter if you release to QA
testers or directly to an unsuspecting public (the norm), such a
strategy is obviously error-prone and self-defeating (not to mention
silly). Error in line 1! :)

But I digress. What about IE 7 and under? Same pattern, different
objects (ActiveX to be specific). If you must provide an "Ajax
experience" for those stuck with these older IE versions, put the
ActiveX crap in a script inside conditional comments.

I'm thinking that's another option for the builder (splitting the
build in two, with one file to be included in IE conditional
comments). If that's too hard for the average Web developer to deal
with, it could also spit out an HTML template that includes the two
scripts. They will certainly need that for the next installment,
which is dealing with the onload "problem".

In my experience, such IE fallback scripts are usually aimed at 8 and
under, but this one is only needed for 6 and under, so do this:-

if (!createXhrObject) {

// Create ActiveX rendition here

}

....or break it out into another file, aimed only at IE 7 and under.

If you need to use the "file" protocol, drop the check for
createXhrObject and set that reference to null before starting the
ActiveX tests (In IE, you need the ActiveX version of XHR to do "file"
requests).

It's worth noting that much of the feature detection/testing is
unneeded in these IE fallback renditions if they are to be included
exclusively in conditional comments. I suppose that's another option
for the builder.

As for sending a request and reacting to the asynchronous (and they
must *always* be asynchronous) callbacks, I'll leave that for another
time. Look into the send method of the Requester object (in My
Library) for the basics. Having used that object (one of the small
bits of My Library that I actually still use) on some recent projects,
I noticed a design mistake: there should never have been an "oncancel"
callback and there should have been an "ontimeout" callback (currently
"oncancel" serves both). The cancel operation is programmatic and the
timeout is not. So the former doesn't need a callback as it is rarely
useful to call your code back due to its own actions. In this case,
it can lead to some ugly logic in the "oncancel" callback (to
determine whether the call is a result of a timeout or not).

What can you expect from the typical do-everything library or
framework? Again, God only knows. For the longest time, jQuery
didn't account for the fact that the ActiveX objects could blow up (no
try-catch until about the fifth version). Things like Dojo and YUI
use browser sniffing and such projects are too large to recover from
such mistakes; there's just not enough interested parties to go back
and rewrite all of the botched code. I tried to fix Dojo once, but
the authors were steadfastly opposed to "patches" that involved more
than a few lines of code at a time. I don't know if that was because
of general delusion or just the fear of appearing incompetent, but at
that rate, it would have taken thirty years to shake all of the bugs
out. So their developers' egos were saved, but when IE 9 hit, every
Dojo application ever written (all ten) fell apart. :(

Interestingly enough, Dojo's XHR "module" actually requires their
botched query module. That's the "best minds in the industry for
you": not thinking beyond today at all; and not thinking much today
either. How many versions of Dojo and YUI actually turned out to be
reusable (beyond the brand name)? Redo-able is what they meant. ;)

http://www.cinsoft.net/
http://twitter.com/cinsoft
http://jsperf.com/browse/david-mark
 
A

Andreas Bergmaier

David said:
How to Create an XHR (Ajax) Object


var xhrCreated;

// As always, shorthand feature detection for example--use
isHostMethod in this case

// Degrades in IE 6- :)

if (this.XMLHttpRequest)

// Use try-catch as this host object may explode on creation

try {
var x = new XMLHttpRequest();
xhrCreated = true;
} catch(e) {
}
}

if (xhrCreated) {
var createXhrObject = function() {
return new XMLHttpRequest();
};
}

if (createXhrObject) {

// Put app that must create XHR objects here

}

In my experience, such IE fallback scripts are usually aimed at 8 and
under, but this one is only needed for 6 and under, so do this:-

if (!createXhrObject) {

// Create ActiveX rendition here

}

What do you think about

if (!window.XMLHttpRequest)
window.XMLHttpRequest = function() {
try {
return new (window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(this, "Microsoft.XMLHTTP");
});
} catch(e) {
try {
return new (window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(this, "Msxml2.XMLHTTP");
});
} catch(e) {
delete window.XMLHttpRequest;
throw new Error("This browser doesn't support any AJAX");
}
}
};

And then just use new XMLHttpRequest() whereever you need it?

Bergi
 
D

David Mark

David Mark schrieb:






















What do you think about

if (!window.XMLHttpRequest)
    window.XMLHttpRequest = function() {

Don't augment host objects and don't obscure browser differences.
       try {
          return new (window.XMLHttpRequest = function XMLHttpRequest() {
             ActiveXObject.call(this, "Microsoft.XMLHTTP");
          });

Why are you setting the property again?
       } catch(e) {
          try {
             return new (window.XMLHttpRequest = functionXMLHttpRequest() {
                ActiveXObject.call(this, "Msxml2.XMLHTTP");
             });
          } catch(e) {
             delete window.XMLHttpRequest;

Never attempt to delete properties of a host object
             throw new Error("This browser doesn't support any AJAX");

Why do that?
          }
       }
    };

And then just use new XMLHttpRequest() whereever you need it?

You don't want to spoof that method as other scripts may get
confused. The ActiveX implementations are not exactly the same, so
create your own wrapper.
 
T

Thomas 'PointedEars' Lahn

Andreas said:
What do you think about

if (!window.XMLHttpRequest)
window.XMLHttpRequest = function() {
try {
return new (window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(this, "Microsoft.XMLHTTP");
});
} catch(e) {
try {
return new (window.XMLHttpRequest = function XMLHttpRequest()
{
ActiveXObject.call(this, "Msxml2.XMLHTTP");
});
} catch(e) {
delete window.XMLHttpRequest;
throw new Error("This browser doesn't support any AJAX");
}
}
};

And then just use new XMLHttpRequest() whereever you need it?

I think it is utter nonsense. I wonder, have you even tried it?

- You are testing a property of the object referred to by `window' when you
are interested in a property of the global object.

- You are attempting to augment the host object referred to by `window'.

- Calling a callable object as property of another object is _not_
equivalent to using it as a constructor.

- You are calling `call' on a host object, but it does not need to inherit
from Function.prototype or otherwise implement that method. Since
`ActiveXObject' implements the method, but does not allow it to be called
on that object ("Automation server cannot create object"), assuming that
no other host-object specific peculiarities are involved, the first
attempt to use `new window.XMLHttpRequest' will delete
`window.XMLHttpRequest' and throw an Error exception; if that exception
is handled, subsequent attempts will throw a built-in TypeError exception.

- The `call' call is pointless. Your constructor does _not_ *return* a
reference to another object (as a factory would), so it returns a
reference to an "empty" Object instance:

return new (window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(this, "Microsoft.XMLHTTP");
});

evaluates to

window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(this, "Microsoft.XMLHTTP");
};

return new window.XMLHttpRequest();

which, assuming for a moment `call' works here, can be broken down into

var previousXHR = window.XMLHttpRequest;

window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(previousXHR, "Microsoft.XMLHTTP");
};

return new window.XMLHttpRequest();

which evaluates to the equivalent of

return {};

As a result, attempts to call XHR methods on the newly created object will
throw a built-in TypeError exception, while attempts to set event
listeners or other non callable properties will probably work but will do
nothing useful.

In case you have problems seeing any of the above, replace the relevant
parts with dummy parts that work basically the same:

var w = {},
global = this;

function A ()
{
console.log("A: ", arguments);

/* Disable this to see what happened if call() would work */
if (this != global)
{
throw new Error("Automation server cannot create object");
}
}

A.prototype.open = function () {};

if (!w.X)
w.X = function () {
try
{
return new (w.X = function X () {
A.call(this, "Microsoft.XMLHTTP");
});
}
catch (e) {
try
{
return new (w.X = function X () {
A.call(this, "Msxml2.XMLHTTP");
});
}
catch (e)
{
delete w.X;
throw new Error("This browser doesn't support any AJAX");
}
}
};

/* Disable this to see what happened if exception was not handled */
try
{
new w.X();
}
catch (e)
{
}

var x = new w.X();

/* TypeError */
x.open();

- You are using a named function expression. Doing that, you are
effectively declaring a function with that name in the local execution
context, due to a JScript bug (which is the ECMAScript implementation of
about the only environment that this code would be relevant for). This is
even more foolish as you are not referring to the function name in the
function (recursion without arguments.callee).

- The `XMLHttpRequest' behavior in JScript/MSHTML varies from that in MSXML,
and might even vary more from it in the future (when XMLHttpRequest2 might
be implemented). So you need to encapsulate this. (ISTM David's original
approach already falls short of that distinction.)

So you *really* do *not* want to do this.


PointedEars
 
T

Thomas 'PointedEars' Lahn

Thomas said:
- The `call' call is pointless. Your constructor does _not_ *return* a
reference to another object (as a factory would), so it returns a
reference to an "empty" Object instance:

return new (window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(this, "Microsoft.XMLHTTP");
});

evaluates to

window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(this, "Microsoft.XMLHTTP");
};

return new window.XMLHttpRequest();

That is not entirely accurate (`x = y` evaluates to the value of _`y'_ if
not in JavaScript 1.2 compliance mode), but you get the idea.
which, assuming for a moment `call' works here, can be broken down into

var previousXHR = window.XMLHttpRequest;

window.XMLHttpRequest = function XMLHttpRequest() {
ActiveXObject.call(previousXHR, "Microsoft.XMLHTTP");
};

return new window.XMLHttpRequest();

which evaluates to the equivalent of

return {};

PointedEars
 
D

David Mark

- The `XMLHttpRequest' behavior in JScript/MSHTML varies from that in MSXML,
  and might even vary more from it in the future (when XMLHttpRequest2 might
  be implemented).  So you need to encapsulate this.  (ISTM David'soriginal
  approach already falls short of that distinction.)

No overrideMimeType? That should certainly be part of the
documentation. And it's a hack method anyway.
 
T

Thomas 'PointedEars' Lahn

David said:
No overrideMimeType? That should certainly be part of the
documentation. And it's a hack method anyway.

`file://', for a start.


PointedEars
 
T

Thomas 'PointedEars' Lahn

David said:
I already covered that one. ;)

No, a code branch that uses ActiveXObject(…) instead of XMLHttpRequest() if
available or if the URI is a `file:' URI is missing from your suggestion.
It is insufficient, and inappropriate, for a library to throw an exception
when it could work if done differently.

In JSX:http.js, I am creating XHR objects not before they are actually used,
and reuse it if possible. This would allow me to use the type of object
that fits the request URI best (the current HEAD revision has other flaws,
though). However, for the time being I have opted not to use
`XMLHttpRequest' when `ActiveXObject' is available and its use is
successful.

<http://pointedears.de/websvn/filedetails.php?repname=JSX&path=/trunk/http.js>


PointedEars
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,731
Messages
2,569,432
Members
44,834
Latest member
BuyCannaLabsCBD

Latest Threads

Top