Agregue un "gancho" a todas las solicitudes AJAX en una página

102

Me gustaría saber si es posible "conectar" en cada solicitud AJAX (ya sea cuando está a punto de ser enviada o en eventos) y realizar una acción. En este punto, supongo que hay otros scripts de terceros en la página. Algunos de estos pueden usar jQuery, mientras que otros no. es posible?

Yuliy
fuente
Es posible con jQuery, por lo que es posible con javascript simple y antiguo, pero necesitaría tener al menos 2 "ganchos" para cada uno de ellos. De todos modos, ¿por qué usar ambos en la misma página?
yoda
2
¿Qué tal usar esta biblioteca? github.com/slorber/ajax-interceptor
Sebastien Lorber
Esta es una buena solución: stackoverflow.com/questions/25335648/…
phazei
2
Nota: Las respuestas a esta pregunta no cubren las llamadas Ajax realizadas desde la fetch()API más nueva ahora en los navegadores modernos.
jfriend00
1
@ nirvana-msu - Quizás esto: github.com/werk85/fetch-intercept/blob/develop/src/index.js
jfriend00

Respuestas:

109

Inspirado por la respuesta de aviv , investigué un poco y esto es lo que se me ocurrió.
No estoy seguro de que sea tan útil según los comentarios en el script y, por supuesto , solo funcionará para navegadores que utilicen un objeto XMLHttpRequest nativo .
Creo que funcionará si se usan bibliotecas de JavaScript, ya que usarán el objeto nativo si es posible.

function addXMLRequestCallback(callback){
    var oldSend, i;
    if( XMLHttpRequest.callbacks ) {
        // we've already overridden send() so just add the callback
        XMLHttpRequest.callbacks.push( callback );
    } else {
        // create a callback queue
        XMLHttpRequest.callbacks = [callback];
        // store the native send()
        oldSend = XMLHttpRequest.prototype.send;
        // override the native send()
        XMLHttpRequest.prototype.send = function(){
            // process the callback queue
            // the xhr instance is passed into each callback but seems pretty useless
            // you can't tell what its destination is or call abort() without an error
            // so only really good for logging that a request has happened
            // I could be wrong, I hope so...
            // EDIT: I suppose you could override the onreadystatechange handler though
            for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
                XMLHttpRequest.callbacks[i]( this );
            }
            // call the native send()
            oldSend.apply(this, arguments);
        }
    }
}

// e.g.
addXMLRequestCallback( function( xhr ) {
    console.log( xhr.responseText ); // (an empty string)
});
addXMLRequestCallback( function( xhr ) {
    console.dir( xhr ); // have a look if there is anything useful here
});
maullar
fuente
Sería bueno extender esta respuesta para admitir ganchos posteriores a la solicitud
Sebastien Lorber
1
Según su implementación, he publicado algo en NPM que funciona tanto con solicitudes como con respuestas. github.com/slorber/ajax-interceptor
Sebastien Lorber
4
console.log (xhr.responseText) es una cadena vacía porque en ese momento xhr está vacío. si pasa la variable xhr a la variable global y establece un retraso de unos segundos, podrá acceder a las propiedades directamente
Toolkit
5
buena respuesta. Si desea obtener los datos de respuesta, verifique readyState y el estado cuando el estado cambió. xhr.onreadystatechange = function () {if (xhr.readyState == 4 && xhr.status == 200) {console.log (JSON.parse (xhr.responseText)); }}
Stanley Wang
1
@ucheng Si agregamos onreadystatechange de xhr, ¿anulará el método de cambio de estado listo original? Utilice mejor xhrObj.addEventListener ("load", fn)
Mohamed Hussain
125

NOTA: La respuesta aceptada no da la respuesta real porque se llama demasiado pronto.

Puede hacer esto, lo que genéricamente interceptará cualquier AJAX a nivel mundial y no estropeará ninguna devolución de llamada, etc.que tal vez haya sido asignada por bibliotecas AJAX de terceros.

(function() {
    var origOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function() {
        console.log('request started!');
        this.addEventListener('load', function() {
            console.log('request completed!');
            console.log(this.readyState); //will always be 4 (ajax is completed successfully)
            console.log(this.responseText); //whatever the response was
        });
        origOpen.apply(this, arguments);
    };
})();

Algunos documentos más de lo que puede hacer aquí con la API addEventListener aquí:

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Monitoring_progress

(Tenga en cuenta que esto no funciona <= IE8)

John Culviner
fuente
¡Gracias! Eso funciona perfectamente. Solo modifico un poco: this.statusdevuelvo el estado del servidor en este caso 200si la solicitud es correcta, 404 si no se encontró el elemento, 500, etc. Pero el código funciona perfectamente.
MrMins
1
Esto puede parecer viejo para ti, pero te mereces todo el amor por esto. Estaba profundamente estancado. Gracias un montón.
Carles Alcolea
Simplemente porque he buscado un poco esto. El "load"evento solo se llama al éxito. Si no le importa el resultado (solo que la consulta terminó), puede usar el "loadend"evento
Romuald Brunet
Me tomó muchos días encontrar esto. Gracias por esta genialidad.
David
Probé muchas respuestas y esto funciona perfectamente con WKWebView. Tuve problemas para usar eventos Ajax ya que es posible que jQuery no se cargue antes de llamar a AssessJavascript o usar addUserScript. ¡Gracias!
Jake Cheng
21

Ya que mencionas jQuery, sé ofertas jquery un .ajaxSetup()método que establece opciones ajax globales que incluyen los activadores de eventos como success, errory beforeSend- que es lo que suena como lo que busca.

$.ajaxSetup({
    beforeSend: function() {
        //do stuff before request fires
    }
});

por supuesto, necesitará verificar la disponibilidad de jQuery en cualquier página en la que intente utilizar esta solución.

jondavidjohn
fuente
1
Gracias por la sugerencia, pero desafortunadamente esto no intercepta las llamadas AJAX que se realizan sin usar AJAX.
Yuliy
8
En las noticias de hoy: Unicornios asesinados por llamadas AJAX que se realizan sin usar AJAX
Dashu
3
no quiere decir objeto jquery AJAX. Pero incluso entonces, usarás la misma instancia de jquery cada vez
Shishir Arora
1
Muy útil. Quería agregar, otro disparador es statusCode. Conectando algo como statusCode: {403: function () {error msg} puede proporcionar una verificación global de autenticación / permisos / rol para todas las funciones ajax sin tener que volver a escribir una sola solicitud .ajax.
Watercayman
8

Hay un truco para hacerlo.

Antes de que se ejecuten todos los scripts, tome el objeto XHMHttpReuqest original y guárdelo en una var. Luego anule el XMLHttpRequest original y dirija todas las llamadas a él a través de su propio objeto.

Código Psuedo:

 var savd = XMLHttpRequest;
 XMLHttpRequest.prototype = function() {
         this.init = function() {
         }; // your code
         etc' etc'
 };
aviv
fuente
10
Esta respuesta no es del todo correcta, si cambia el prototipo de un objeto, incluso se cambiará el guardado. Además, todo el prototipo está siendo reemplazado por una función que romperá todas las solicitudes de ajax. Sin embargo, sí me inspiró a ofrecer una respuesta.
maullido
8

Encontré una buena biblioteca en Github que hace bien el trabajo, debes incluirla antes que cualquier otro archivo js

https://github.com/jpillora/xhook

aquí hay un ejemplo que agrega un encabezado http a cualquier respuesta entrante

xhook.after(function(request, response) {
  response.headers['Foo'] = 'Bar';
});
Mohamed Selim
fuente
2
¡Muy buena! ¡Pero no funcionó en la aplicación Cordova incluso con el último complemento de vista web de Chrome del paso de peatones!
Islam Attrash
1
Esto funcionó perfectamente para mis necesidades particulares: En Wordpress, filtrado de eventos del calendario completo del complemento Organizador de eventos
Alfredo Yong
No funciona para todas las solicitudes, algunas búsquedas y xhr están bien, pero, por ejemplo, no captura xhr de google recaptcha
Sergio Quintero
@SergioQuintero: Supongo que este es su problema de GitHub: github.com/jpillora/xhook/issues/102 . Considere eliminar su comentario aquí, ya que no fue un problema con la biblioteca.
thirtydot
@SergioQuintero Cualquier anulación de las interfaces XHR solo ocurre en ese documento, no globalmente en el navegador porque eso sería un enorme agujero de seguridad / privacidad. reCAPTCHA se ejecuta en un iframe y no puede ejecutar la anulación allí.
Walf
5

Usando la respuesta de "meouw", sugiero usar la siguiente solución si desea ver los resultados de la solicitud

function addXMLRequestCallback(callback) {
    var oldSend, i;
    if( XMLHttpRequest.callbacks ) {
        // we've already overridden send() so just add the callback
        XMLHttpRequest.callbacks.push( callback );
    } else {
        // create a callback queue
        XMLHttpRequest.callbacks = [callback];
        // store the native send()
        oldSend = XMLHttpRequest.prototype.send;
        // override the native send()
        XMLHttpRequest.prototype.send = function() {
            // call the native send()
            oldSend.apply(this, arguments);

            this.onreadystatechange = function ( progress ) {
               for( i = 0; i < XMLHttpRequest.callbacks.length; i++ ) {
                    XMLHttpRequest.callbacks[i]( progress );
                }
            };       
        }
    }
}

addXMLRequestCallback( function( progress ) {
    if (typeof progress.srcElement.responseText != 'undefined' &&                        progress.srcElement.responseText != '') {
        console.log( progress.srcElement.responseText.length );
    }
});
heroína
fuente
3

jquery ...

<script>
   $(document).ajaxSuccess(
        function(event, xhr, settings){ 
          alert(xhr.responseText);
        }
   );
</script>
Vladimir Miroshnichenko
fuente
1
jQuery no detectará solicitudes realizadas con otras bibliotecas. Por ejemplo, ExtJS. Si solo está usando jQuery, es una buena respuesta. De lo contrario, no funcionará todo el tiempo.
npiani
1
ni siquiera captará solicitudes de jquery si la instancia de jquery es diferente. Cambio de prototipo si es necesario
Shishir Arora
3

Además de la respuesta de meouw, tuve que inyectar código en un iframe que intercepta las llamadas XHR y usé la respuesta anterior. Sin embargo, tuve que cambiar

XMLHttpRequest.prototype.send = function(){

A:

XMLHttpRequest.prototype.send = function(body)

Y tuve que cambiar

oldSend.apply(this, arguments);

A:

oldSend.call(this, body);

Esto era necesario para que funcionara en IE9 con el modo de documento IE8 . Si no se realizó esta modificación, algunas devoluciones de llamada generadas por el marco de componentes (Visual WebGUI) no funcionaron. Más información en estos enlaces:

Sin estas modificaciones, las devoluciones de datos de AJAX no terminaron.

Walf
fuente