41

Is there any way to detect global AJAX calls (particularly responses) on a web page with generic JavaScript (not with frameworks)?

I've already reviewed the question "JavaScript detect an AJAX event", here on StackOverflow, and tried patching in the accepted answer's code into my application but it didn't work. I've never done anything with AJAX before either so, I don't know enough to modify it to work.

I don't need anything fancy, I just need to detect all (specific, actually, but I'd have to detect all first and go from there) AJAX responses and patch them into an IF statement for use. So, eventually, I'd like something like:

if (ajax.response == "certainResponseType"){
    //Code
}

, for example.

Update: It seems I should clarify that I'm not trying to send a request - I'm developing a content script and I need to be able to detect the web page's AJAX requests (not make my own), so I can execute a function when a response is detected.

Community
  • 1
  • 1
mythofechelon
  • 3,692
  • 11
  • 37
  • 48
  • 2
    I always extensively Google these things before I end up here. I even checked StackOverflow itself before I posted. – mythofechelon May 28 '12 at 11:12
  • About your Update I don't think that this is possible outside of a browser extension. – rekire May 28 '12 at 11:31
  • 1
    You don't think it's not possible for a browser extension? I can't see why not, mind. It just runs standard JavaScript. – mythofechelon May 28 '12 at 11:33
  • If I understood you right: I wanted to say that I think that is *only* possible in a browser extension. – rekire May 28 '12 at 11:43
  • There shouldn't be a problem then, as a content script is part of a browser extension. (Sorry about the confusing nature of my last comment. I edited it and forgot to remove the first "not") – mythofechelon May 28 '12 at 11:45
  • Is this still open, or have you found a solution? – serv-inc Sep 04 '15 at 10:31

4 Answers4

48

Here's some code (tested by pasting into Chrome 31.0.1650.63's console) for catching and logging or otherwise processing ajax requests and their responses:

(function() {
    var proxied = window.XMLHttpRequest.prototype.send;
    window.XMLHttpRequest.prototype.send = function() {
        console.log( arguments );
        //Here is where you can add any code to process the request. 
        //If you want to pass the Ajax request object, pass the 'pointer' below
        var pointer = this
        var intervalId = window.setInterval(function(){
                if(pointer.readyState != 4){
                        return;
                }
                console.log( pointer.responseText );
                //Here is where you can add any code to process the response.
                //If you want to pass the Ajax request object, pass the 'pointer' below
                clearInterval(intervalId);

        }, 1);//I found a delay of 1 to be sufficient, modify it as you need.
        return proxied.apply(this, [].slice.call(arguments));
    };


})();

This code solves the above issue with the accepted answer:

Note that it may not work if you use frameworks (like jQuery), because they may override onreadystatechange after calling send (I think jQuery does). Or they can override send method (but this is unlikely). So it is a partial solution.

Because it does not rely on the 'onreadystatechange' callback being un-changed, but monitors the 'readyState' itself.

I adapted the answer from here: https://stackoverflow.com/a/7778218/1153227

Community
  • 1
  • 1
Omn
  • 2,982
  • 1
  • 26
  • 39
36

Gives this a try. Detects Ajax responses, then I added a conditional using the XMLHttpRequest propoerties readyState & status to run function if response status = OK

var oldXHR = window.XMLHttpRequest;

function newXHR() {
    var realXHR = new oldXHR();
    realXHR.addEventListener("readystatechange", function() {
        if(realXHR.readyState==4 && realXHR.status==200){
            afterAjaxComplete() //run your code here
        }
    }, false);
    return realXHR;
}
window.XMLHttpRequest = newXHR;

Modified from: Monitor all JavaScript events in the browser console

Community
  • 1
  • 1
mmguy
  • 361
  • 3
  • 3
  • This worked for me. Tested on stackoverflow's requests, and it detected the complete state reliably. The snipped from the accepted answer from freakish however did not work for me. – Marcel Sep 22 '16 at 20:34
  • This is exactly what I needed! – Vegeta ZA May 04 '21 at 08:45
21

This can be a bit tricky. How about this?

var _send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {

    /* Wrap onreadystaechange callback */
    var callback = this.onreadystatechange;
    this.onreadystatechange = function() {             
         if (this.readyState == 4) {
             /* We are in response; do something,
                like logging or anything you want */
         }
         callback.apply(this, arguments);
    }

    _send.apply(this, arguments);
}

I didn't test it, but it looks more or less fine.

Note that it may not work if you use frameworks (like jQuery), because they may override onreadystatechange after calling send (I think jQuery does). Or they can override send method (but this is unlikely). So it is a partial solution.

EDIT: Nowadays (the begining of 2018) this gets more complicated with the new fetch API. Global fetch function has to be overridden as well in a similar manner.

freakish
  • 54,167
  • 9
  • 132
  • 169
  • @BenHooper Because it does not do anything at the moment - you need to add your own code in `onreadystatechange` handler. It works for me (just tested it). Are you using frameworks? – freakish May 28 '12 at 11:41
  • Sorry, I should have mentioned that I replaced the comment with a simple `alert("AJAX");`. Still got nothing. Also, I have updated my question with more information now. – mythofechelon May 28 '12 at 11:45
  • @BenHooper There are three possibilites: (1) a framework overrides XMLHttpRequest object (if it does, then I'm affraid there is nothing you can do :( unless you override the framework :] ), (2) AJAX request didn't end, (3) you are trying cross-domain AJAX (but then you would see an error in console). Try putting `alert` outside of `if` and see what happens (by the way: there was a typo in my code: last line had triple `p`). – freakish May 28 '12 at 11:50
  • (1) I'm not actively using a Framework (in fact, I'm actively trying not to), but Chrome's framework may be interfering. (2) The website's content was loaded, so it should have ended. (3) Yes, I noticed and changed it. I'll try what you suggested now. :) – mythofechelon May 28 '12 at 12:00
  • Mmm.. Still doesn't do anything. :| – mythofechelon May 28 '12 at 15:11
  • @BenHooper It is possible that Framework (jQuery?) is overriding this functions. You don't need to use it actively, it may be enough if you include `script` tag. And as I said: if this is the case, then you won't make it in a general way (or at least I do not know a way). That's everything I can do for you, sorry, mate. – freakish May 28 '12 at 15:17
  • The extension is just standard HTML, CSS and JavaScript. The only framework that's being used is Google Chrome's, so yes maybe. Okay, well, thank you for trying. I'll leave the question open a bit longer just to see if anyone has a compatible solution. If no other answers present I'll give you the accepted answer. – mythofechelon May 31 '12 at 13:33
  • it is wrking fine but, How to get URL of each called service.?? – sudhakar phad Mar 01 '18 at 09:07
  • @sudhakarphad For that you can overwrite `.open` method (in a similar manner) and store the url somewhere, possibly on the xhr object itself. Then you can retrieve it in the overwritten `.send`. Depending on your needs you can totally ignore `.send` method and work with `.open` method only. Note that `.send` is the method that actually sends data to the server side. – freakish Mar 01 '18 at 09:20
10

A modern (as of April 2021) answer to the question is to use PerformanceObserver which lets you observe both XMLHttpRequest requests and fetch() requests:

<!-- Place this at the top of your page's <head>: -->
<script type="text/javascript">
var myRequestLog = []; // Using `var` (instead of `let` or `const`) so it creates an implicit property on the (global) `window` object so you can easily access this log from anywhere just by using `window.myRequestLog[...]`.
function onRequestsObserved( batch ) {
    myRequestLog.push( ...batch.getEntries() );
}
var requestObserver = new PerformanceObserver( onRequestsObserved );
requestObserver.observe( { type: 'resource' /*, buffered: true */ } );
</script>

I use the above snippet in my pages to log requests so I can report them back to the mothership in my global window.addEventListenr('error', ... ) callback.


  • The batch.getEntries() function returns an array of DOM PerformanceResourceTiming objects (because we're only listening to type: 'resource', otherwise it would return an array of differently-typed objects).
  • Each PerformanceResourceTiming object has useful properties like:
    • The initiatorType property can be:
      • A HTML element name (tag name) if the request was caused by an element:
        • 'link' - Request was from a <link> element in the page.
        • 'script' - Request was to load a <script>.
        • 'img' - Request was to load an <img /> element.
        • etc
      • 'xmlhttprequest' - Request was caused by a XMLHttpRequest invocation.
      • 'fetch' - Request was caused by a fetch() call.
    • name - The URI of the resource/request. (If there's a redirection I'm unsure if this is the original request URI or the final request URI).
    • startTime: Caution: this is actually the time since PerformanceObserver.observe() was called when the request was started.
    • duration: Caution: this is actually the time since PerformanceObserver.observe() was called when the request completed: it is not the duration of the request alone. To get the "real" duration you need to subtract startTime from duration.
    • transferSize: the number of bytes in the response.
Dai
  • 141,631
  • 28
  • 261
  • 374