How would go about monkey patching the XMLHTTPRequest's onreadystatechange function. I'm trying to add a function that would be called when every ajax request made from a page come back.

I know this sounds like a terrible idea, but the use case is quite peculiar. I want to use a certain SDK with a console (jqconsole) but show status and results from ajax calls within the console without modifying the external SDK.

I've looked at this post which had great info, but nothing on monkey patching the callback which seem to exceed my JavaScript skills.

P.S Can't use jQuery since it only supports ajax calls made from jQuery not from XMLHTTPRequests directly which is the case here.

有帮助吗?

解决方案

To monkey-patch XMLHttpRequests, you need to know how an AJAX request is generally constructed:

  1. Constructor invocation
  2. Preparation the request (setRequestHeader(), open())
  3. Sending the request (.send).

General-purpose patch

(function(xhr) {
    function banana(xhrInstance) { // Example
        console.log('Monkey RS: ' + xhrInstance.readyState);
    }
    // Capture request before any network activity occurs:
    var send = xhr.send;
    xhr.send = function(data) {
        var rsc = this.onreadystatechange;
        if (rsc) {
            // "onreadystatechange" exists. Monkey-patch it
            this.onreadystatechange = function() {
                banana(this);
                return rsc.apply(this, arguments);
            };
        }
        return send.apply(this, arguments);
    };
})(XMLHttpRequest.prototype);

The previous assumed that onreadystatechange was assigned to the onreadystatechange handler. For simplicity, I didn't include the code for other events, such as onload. Also, I did not account for events added using addEventListener.

The previous patch runs for all requests. But what if you want to limit the patch to a specific request only? A request with a certain URL or async flag and a specific request body?

Conditional monkey-patch

Example: Intercepting all POST requests whose request body contains "TEST"

(function(xhr) {
    function banana(xhrInstance) { // Example
        console.log('Monkey RS: ' + xhrInstance.readyState);
    }
    // 
    var open = xhr.open;
    xhr.open = function(method, url, async) {
        // Test if method is POST
        if (/^POST$/i.test(method)) {
            var send = this.send;
            this.send = function(data) {
                // Test if request body contains "TEST"
                if (typeof data === 'string' && data.indexOf('TEST') >= 0) {
                    var rsc = this.onreadystatechange;
                    if (rsc) {
                        // Apply monkey-patch
                        this.onreadystatechange = function() {
                            banana(this);
                            return rsc.apply(this, arguments);
                        };
                    }
                }
                return send.apply(this, arguments);
            };
        }
        return open.apply(this, arguments);
    };
})(XMLHttpRequest.prototype);

The main techniques used is the transparent rewrite using...

var original = xhr.method; 
xhr.method = function(){
    /*...*/;
    return original.apply(this, arguments);
};

My examples are very basic, and can be extended to meet your exact wishes. That's up to you, however.

其他提示

Assuming you can ignore IE...

//Totally untested code, typed at the SO <textarea>... but the concept *should* work, let me know if it doesn't.
var OldXMLRequest = XMLHttpRequest;

// Create a new instance
function XMLHttpRequest() {
  var ajax = new OldXMLRequest();

  // save old function
  var f = ajax.onreadystatechange;
  ajax.onreadystatechange = function() {
    console.log("Whatever!");
    f(); // Call the old function
  }
  return ajax;
}

you can learn from Ajax-hook written by chinese!

it is a advanced js to enable Monkey patch XMLHTTPRequest

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top