Adicione um "gancho" a todas as solicitações AJAX em uma página

102

Gostaria de saber se é possível "enganchar" em cada solicitação AJAX (quando ela está para ser enviada ou em eventos) e executar uma ação. Neste ponto, estou supondo que há outros scripts de terceiros na página. Alguns deles podem usar jQuery, enquanto outros não. Isso é possível?

Yuliy
fonte
É possível com o jQuery, então é possível com o antigo javascript, mas você precisaria ter pelo menos 2 "ganchos" para cada um deles. Enfim, por que usar os dois na mesma página?
yoda
2
Que tal usar esta biblioteca? github.com/slorber/ajax-interceptor
Sebastien Lorber
Esta é uma boa solução: stackoverflow.com/questions/25335648/…
phazei
2
Nota: As respostas a esta pergunta não cobrem chamadas Ajax feitas da fetch()API mais recente agora em navegadores modernos.
jfriend00
1
@ nirvana-msu - Talvez este: github.com/werk85/fetch-intercept/blob/develop/src/index.js
jfriend00

Respostas:

109

Inspirado pela resposta de aviv , eu investiguei um pouco e foi isso que eu descobri.
Não tenho certeza se é tão útil de acordo com os comentários no script e, claro , só funcionará para navegadores que usam um objeto XMLHttpRequest nativo .
Acho que funcionará se as bibliotecas javascript estiverem em uso, pois usarão o objeto nativo, se possível.

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
});
meouw
fonte
seria bom estender esta resposta para apoiar ganchos pós-solicitação
Sebastien Lorber
1
Com base na sua implementação, publiquei algo no NPM que funciona com solicitações e respostas! github.com/slorber/ajax-interceptor
Sebastien Lorber
4
console.log (xhr.responseText) é uma string vazia porque naquele momento xhr está vazio. se você passar a variável xhr para a variável global e definir o atraso de alguns segundos, poderá acessar as propriedades diretamente
Toolkit
5
boa resposta. Se você deseja obter os dados de resposta, verifique o readyState e o status quando o estado for alterado. xhr.onreadystatechange = function () {if (xhr.readyState == 4 && xhr.status == 200) {console.log (JSON.parse (xhr.responseText)); }}
Stanley Wang
1
@ucheng Se adicionarmos onreadystatechange de xhr, ele substituirá o método de mudança de estado pronto original é melhor usar xhrObj.addEventListener ("load", fn)
Mohamed Hussain
125

NOTA: A resposta aceita não produz a resposta real porque está sendo chamada muito cedo.

Você pode fazer isso, o que interceptará genericamente qualquer AJAX globalmente e não prejudicará nenhum retorno de chamada, etc. que possa ter sido atribuído por qualquer biblioteca AJAX de terceiros.

(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);
    };
})();

Mais alguns documentos do que você pode fazer aqui com a API addEventListener aqui:

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

(Observe que isso não funciona <= IE8)

John Culviner
fonte
Obrigado! Isso funciona perfeitamente. Só modifico um pouco: this.statusdevolve o estado do servidor neste caso 200se o pedido está OK, 404 se o elemento não foi encontrado, 500, etc. Mas o código funciona perfeitamente.
MrMins
1
Isso pode parecer antigo para você, mas você merece todo o amor por isso. Eu estava profundamente preso. Muitíssimo obrigado.
Carles Alcolea
Simplesmente porque pesquisei um pouco por isso. O "load"evento só é convocado com sucesso. Se você não se importar com o resultado (apenas que a consulta acabou), você pode usar o "loadend"evento
Romuald Brunet
Demorou muitos dias para encontrar isso. Obrigado por este pedaço de gênio.
David,
Tentei muitas respostas e isso funciona perfeitamente com WKWebView. Eu estava tendo problemas para usar eventos Ajax, pois jQuery não pode ser carregado antes de eu chamar o evaluateJavascript ou usar o addUserScript. Obrigado!
Jake Cheng
21

Desde que você mencionou jQuery, eu sei ofertas jquery um .ajaxSetup()método que define as opções ajax globais que incluem os gatilhos de eventos como success, errore beforeSend- que é o que soa como o que você está procurando.

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

é claro que você precisaria verificar a disponibilidade do jQuery em qualquer página em que tente usar essa solução.

Jondavidjohn
fonte
1
Obrigado pela sugestão, mas isso infelizmente não intercepta chamadas AJAX que não são feitas usando AJAX.
Yuliy,
8
Nas notícias de hoje: Unicórnios mortos por chamadas AJAX que não são feitas usando AJAX
Dashu
3
ele não quer dizer objeto jquery AJAX. Mas mesmo assim, você terá que usar a mesma instância do jquery todas as vezes
Shishir Arora
1
Extremamente útil. Desejado adicionar, outro gatilho é statusCode. Ao conectar algo como statusCode: {403: function () {error msg}, você pode fornecer uma verificação global auth / perms / role para todas as funções ajax sem ter que reescrever uma única solicitação .ajax.
Watercayman
8

Existe um truque para fazer isso.

Antes de todos os scripts serem executados, pegue o objeto XHMHttpReuqest original e salve-o em uma var. Em seguida, substitua o XMLHttpRequest original e direcione todas as chamadas para ele por meio de seu próprio objeto.

Código Psuedo:

 var savd = XMLHttpRequest;
 XMLHttpRequest.prototype = function() {
         this.init = function() {
         }; // your code
         etc' etc'
 };
aviv
fonte
10
Esta resposta não está certa, se você alterar o protótipo de um objeto, até mesmo aquele salvo será alterado. Além disso, todo o protótipo está sendo substituído por uma função que interromperá todas as solicitações ajax. Isso me inspirou a oferecer uma resposta.
meouw
8

Eu encontrei uma boa biblioteca no Github que faz um bom trabalho, você deve incluí-la antes de qualquer outro arquivo js

https://github.com/jpillora/xhook

aqui está um exemplo que adiciona um cabeçalho http a qualquer resposta recebida

xhook.after(function(request, response) {
  response.headers['Foo'] = 'Bar';
});
Mohamed Selim
fonte
2
Muito bom! Mas não funcionou no aplicativo Cordova mesmo com o último plugin de visualização da web do Chrome para crosswalk!
Islam Attrash
1
Isso funcionou perfeitamente para minhas necessidades específicas: No Wordpress, filtrando eventos do calendário completo do plugin do Organizador de Eventos
Alfredo Yong
Não está funcionando para todos os pedidos, alguns fetch e xhr ok, mas por exemplo não pega xhr do google recaptcha
Sergio Quintero
@SergioQuintero: Presumo que este seja o seu problema no GitHub: github.com/jpillora/xhook/issues/102 . Considere excluir seu comentário aqui, uma vez que não foi um problema com a biblioteca.
thirtydot
@SergioQuintero Qualquer substituição de interfaces XHR só acontece nesse documento, não globalmente no navegador, porque isso seria um enorme furo de segurança / privacidade. O reCAPTCHA é executado em um iframe e você não pode executar a substituição nele.
Walf
5

Usando a resposta "meouw", sugiro usar a seguinte solução se quiser ver os resultados do pedido

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
fonte
3

jquery ...

<script>
   $(document).ajaxSuccess(
        function(event, xhr, settings){ 
          alert(xhr.responseText);
        }
   );
</script>
Vladimir Miroshnichenko
fonte
1
O jQuery não detectará solicitações feitas usando outras bibliotecas. Por exemplo ExtJS. Se você está usando apenas jQuery, é uma boa resposta. Caso contrário, não funcionará o tempo todo.
npiani
1
ele nem mesmo pegará solicitações jquery se a instância jquery for diferente. Mudança no protótipo, se necessário
Shishir Arora
3

Além da resposta de meouw, eu tive que injetar código em um iframe que intercepta chamadas XHR e usei a resposta acima. No entanto, eu tive que mudar

XMLHttpRequest.prototype.send = function(){

Para:

XMLHttpRequest.prototype.send = function(body)

E eu tive que mudar

oldSend.apply(this, arguments);

Para:

oldSend.call(this, body);

Isso foi necessário para fazê-lo funcionar no IE9 com o modo de documento do IE8 . Se essa modificação não foi feita, alguns retornos de chamada gerados pela estrutura do componente (Visual WebGUI) não funcionaram. Mais informações nestes links:

Sem essas modificações, os postbacks de AJAX não foram encerrados.

Walf
fonte