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

Discussion in 'Javascript' started by David Mark, Dec 7, 2011.

  1. David Mark

    David Mark Guest

    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
    David Mark, Dec 7, 2011
    #1
    1. Advertising

  2. Re: How to Create an XHR (Ajax) Object

    David Mark schrieb:
    > 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
    Andreas Bergmaier, Dec 8, 2011
    #2
    1. Advertising

  3. David Mark

    David Mark Guest

    Re: How to Create an XHR (Ajax) Object

    On Dec 7, 11:41 pm, Andreas Bergmaier <> wrote:
    > David Mark schrieb:
    >
    >
    >
    >
    >
    >
    >
    >
    >
    > > 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() {


    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.
    David Mark, Dec 8, 2011
    #3
  4. Re: How to Create an XHR (Ajax) Object

    Andreas Bergmaier wrote:

    > David Mark schrieb:
    >> 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?


    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
    --
    realism: HTML 4.01 Strict
    evangelism: XHTML 1.0 Strict
    madness: XHTML 1.1 as application/xhtml+xml
    -- Bjoern Hoehrmann
    Thomas 'PointedEars' Lahn, Dec 8, 2011
    #4
  5. Re: How to Create an XHR (Ajax) Object

    Thomas 'PointedEars' Lahn wrote:

    > - 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
    --
    Prototype.js was written by people who don't know javascript for people
    who don't know javascript. People who don't know javascript are not
    the best source of advice on designing systems that use javascript.
    -- Richard Cornford, cljs, <f806at$ail$1$>
    Thomas 'PointedEars' Lahn, Dec 8, 2011
    #5
  6. David Mark

    David Mark Guest

    Re: How to Create an XHR (Ajax) Object

    On Dec 8, 5:12 pm, Thomas 'PointedEars' Lahn <>
    wrote:

    > - 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.
    David Mark, Dec 9, 2011
    #6
  7. Re: How to Create an XHR (Ajax) Object

    David Mark wrote:

    > Thomas 'PointedEars' Lahn wrote:
    >> - 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.)

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


    `file://', for a start.


    PointedEars
    --
    Prototype.js was written by people who don't know javascript for people
    who don't know javascript. People who don't know javascript are not
    the best source of advice on designing systems that use javascript.
    -- Richard Cornford, cljs, <f806at$ail$1$>
    Thomas 'PointedEars' Lahn, Dec 9, 2011
    #7
  8. David Mark

    David Mark Guest

    Re: How to Create an XHR (Ajax) Object

    On Dec 9, 5:50 am, Thomas 'PointedEars' Lahn <>
    wrote:
    > David Mark wrote:
    > > Thomas 'PointedEars' Lahn wrote:
    > >> - 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.)

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

    >
    > `file://', for a start.


    I already covered that one. ;)
    David Mark, Dec 9, 2011
    #8
  9. Re: How to Create an XHR (Ajax) Object

    David Mark wrote:

    > Thomas 'PointedEars' Lahn wrote:
    >> David Mark wrote:
    >> > Thomas 'PointedEars' Lahn wrote:
    >> >> - 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.)

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

    >>
    >> `file://', for a start.

    >
    > 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=%2Ftrunk%2Fhttp.js>


    PointedEars
    --
    When all you know is jQuery, every problem looks $(olvable).
    Thomas 'PointedEars' Lahn, Dec 10, 2011
    #9
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. David Mark
    Replies:
    16
    Views:
    907
    Scott Sauyet
    Nov 11, 2011
  2. David Mark
    Replies:
    58
    Views:
    1,410
    David Mark
    Dec 6, 2011
  3. David Mark
    Replies:
    6
    Views:
    741
    Dr J R Stockton
    Nov 16, 2011
  4. David Mark
    Replies:
    1
    Views:
    757
    David Mark
    Dec 7, 2011
  5. David Mark
    Replies:
    1
    Views:
    706
    dhtml
    Dec 17, 2011
Loading...

Share This Page