Fügen Sie allen AJAX-Anforderungen auf einer Seite einen "Hook" hinzu

102

Ich würde gerne wissen, ob es möglich ist, sich in jede einzelne AJAX-Anfrage "einzuhaken" (entweder während sie gesendet wird oder bei Ereignissen) und eine Aktion auszuführen. An dieser Stelle gehe ich davon aus, dass sich auf der Seite andere Skripte von Drittanbietern befinden. Einige davon verwenden möglicherweise jQuery, andere nicht. Ist das möglich?

Yuliy
quelle
Es ist mit jQuery möglich, also ist es mit einfachem altem Javascript möglich, aber Sie müssten mindestens 2 "Hooks" für jeden von ihnen haben. Wie auch immer, warum beide auf derselben Seite verwenden?
Yoda
2
Wie wäre es mit dieser Bibliothek? github.com/slorber/ajax-interceptor
Sebastien Lorber
Dies ist eine schöne Lösung: stackoverflow.com/questions/25335648/…
Phazei
2
Hinweis: Die Antworten auf diese Frage beziehen sich nicht auf Ajax-Aufrufe, die von der neueren fetch()API in modernen Browsern ausgeführt werden.
jfriend00
1
@ nirvana-msu - Vielleicht das: github.com/werk85/fetch-intercept/blob/develop/src/index.js
jfriend00

Antworten:

109

Inspiriert von Avivs Antwort habe ich ein wenig nachgeforscht und mir das ausgedacht.
Ich bin mir nicht sicher, ob es gemäß den Kommentaren im Skript so nützlich ist und natürlich nur für Browser funktioniert, die ein natives XMLHttpRequest-Objekt verwenden .
Ich denke, es wird funktionieren, wenn Javascript-Bibliotheken verwendet werden, da sie wenn möglich das native Objekt verwenden.

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
quelle
Es wäre schön, diese Antwort zu erweitern, um Post-Request-Hooks zu unterstützen
Sebastien Lorber
1
Basierend auf Ihrer Implementierung habe ich etwas auf NPM veröffentlicht, das sowohl mit Anfragen als auch mit Antworten funktioniert! github.com/slorber/ajax-interceptor
Sebastien Lorber
4
console.log (xhr.responseText) ist eine leere Zeichenfolge, da xhr in diesem Moment leer ist. Wenn Sie die Variable xhr an die globale Variable übergeben und die Verzögerung auf einige Sekunden einstellen, können Sie direkt auf die Eigenschaften zugreifen
Toolkit
5
gute Antwort. Wenn Sie die Antwortdaten abrufen möchten, überprüfen Sie den Status "readyState" und den Status, wenn sich der Status ändert. xhr.onreadystatechange = function () {if (xhr.readyState == 4 && xhr.status == 200) {console.log (JSON.parse (xhr.responseText)); }}
Stanley Wang
1
@ucheng Wenn wir onreadystatechange von xhr hinzufügen, wird es die ursprüngliche Methode zur Änderung des Bereitschaftszustands überschreiben. Verwenden Sie besser xhrObj.addEventListener ("load", fn)
Mohamed Hussain
124

HINWEIS: Die akzeptierte Antwort liefert nicht die tatsächliche Antwort, da sie zu früh aufgerufen wird.

Sie können dies tun , das wird allgemein abfangen jede AJAX global und nicht vermasseln jede Rückrufe usw. , die möglicherweise von Dritten AJAX - Bibliotheken zugewiesen wurden.

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

Weitere Dokumente, was Sie hier mit der addEventListener-API tun können, finden Sie hier:

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

(Beachten Sie, dass dies nicht funktioniert <= IE8)

John Culviner
quelle
Vielen Dank! Das funktioniert perfekt. Ich ändere nur ein wenig: this.statusGeben Sie in diesem Fall den Serverstatus zurück, 200wenn die Anforderung in Ordnung ist, 404, wenn das Element nicht gefunden wurde, 500 usw. Aber der Code funktioniert einwandfrei.
MrMins
1
Das mag sich für dich alt anfühlen, aber du verdienst die ganze Liebe dafür. Ich war tief gefangen. Vielen Dank.
Carles Alcolea
Einfach weil ich ein bisschen danach gesucht habe. Die "load"Veranstaltung ist nur auf Erfolg ausgerichtet. Wenn Sie sich nicht für das Ergebnis interessieren (nur dass die Abfrage beendet wurde), können Sie das "loadend"Ereignis verwenden
Romuald Brunet
Es dauerte viele Tage, um dies zu finden. Vielen Dank für dieses geniale Stück.
David
Ich habe viele Antworten ausprobiert und dies funktioniert perfekt mit WKWebView. Ich hatte Probleme bei der Verwendung von Ajax-Ereignissen, da jQuery möglicherweise nicht geladen wird, bevor ich evaluJavascript aufrufe oder addUserScript verwende. Danke dir!
Jake Cheng
21

Da Sie jquery erwähnen, ich weiß jquery bietet eine .ajaxSetup()Methode , dass Sets globale Ajax - Optionen , die die Ereignisauslöser wie umfassen success, errorund beforeSend- das ist , was so klingt , was Sie suchen.

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

Natürlich müssten Sie die Verfügbarkeit von jQuery auf jeder Seite überprüfen, auf der Sie diese Lösung verwenden möchten.

jondavidjohn
quelle
1
Vielen Dank für den Vorschlag, aber dies fängt leider keine AJAX-Aufrufe ab, die nicht mit AJAX ausgeführt werden.
Yuliy
8
In den heutigen Nachrichten: Einhörner durch AJAX-Anrufe getötet, die nicht mit AJAX ausgeführt werden
Dashu
3
er meint nicht, AJAX-Objekt abzufragen. Aber selbst dann müssen Sie jedes
Mal
1
Sehr hilfreich. Ein weiterer Auslöser ist statusCode. Durch Einstecken von statusCode: {403: function () {error msg} können Sie allen Ajax-Funktionen eine globale Prüfung von auth / perms / role zurückgeben, ohne eine einzige .ajax-Anforderung neu schreiben zu müssen.
Watercayman
8

Es gibt einen Trick, um es zu tun.

Bevor alle Skripte ausgeführt werden, nehmen Sie das ursprüngliche XHMHttpReuqest-Objekt und speichern Sie es in einer anderen Variable. Überschreiben Sie dann die ursprüngliche XMLHttpRequest und leiten Sie alle Aufrufe über Ihr eigenes Objekt an sie weiter.

Pseudocode:

 var savd = XMLHttpRequest;
 XMLHttpRequest.prototype = function() {
         this.init = function() {
         }; // your code
         etc' etc'
 };
aviv
quelle
10
Diese Antwort ist nicht ganz richtig, wenn Sie den Prototyp eines Objekts ändern, wird auch der gespeicherte geändert. Außerdem wird der gesamte Prototyp durch eine Funktion ersetzt, die alle Ajax-Anforderungen unterbricht. Es hat mich jedoch inspiriert, eine Antwort anzubieten.
Meouw
8

Ich habe auf Github eine gute Bibliothek gefunden, die den Job gut macht. Sie müssen sie vor allen anderen JS-Dateien einfügen

https://github.com/jpillora/xhook

Hier ist ein Beispiel, das jeder eingehenden Antwort einen http-Header hinzufügt

xhook.after(function(request, response) {
  response.headers['Foo'] = 'Bar';
});
Mohamed Selim
quelle
2
Tolle Sache! Hat aber auch mit einem neuesten Chrome Web-View-Plugin für Zebrastreifen nicht an der Cordova-Anwendung funktioniert!
Islam Attrash
1
Dies funktionierte perfekt für meine speziellen Anforderungen: In Wordpress, Filtern von Ereignissen aus dem vollständigen Kalender des Events Organizer-Plugins
Alfredo Yong
Funktioniert nicht für alle Anfragen, einige Abrufe und xhr ok, aber zum Beispiel fängt es xhr nicht von Google Recaptcha
Sergio Quintero
@SergioQuintero: Ich gehe davon aus, dass dies Ihr GitHub-Problem ist: github.com/jpillora/xhook/issues/102 . Löschen Sie Ihren Kommentar hier, da dies kein Problem mit der Bibliothek war.
30.
@SergioQuintero Das Überschreiben von XHR-Schnittstellen erfolgt nur in diesem Dokument, nicht global im Browser, da dies eine massive Sicherheits- / Datenschutzlücke darstellen würde. reCAPTCHA wird in einem Iframe ausgeführt und Sie können die Überschreibung dort nicht ausführen.
Walf
5

Mit der Antwort "meouw" schlage ich vor, die folgende Lösung zu verwenden, wenn Sie die Ergebnisse der Anfrage sehen möchten

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 );
    }
});
Heroin
quelle
3

jquery ...

<script>
   $(document).ajaxSuccess(
        function(event, xhr, settings){ 
          alert(xhr.responseText);
        }
   );
</script>
Vladimir Miroshnichenko
quelle
1
jQuery fängt keine Anforderungen ab, die mit anderen Bibliotheken gestellt wurden. Zum Beispiel ExtJS. Wenn Sie nur jQuery verwenden, ist dies eine gute Antwort. Andernfalls funktioniert es nicht immer.
Npiani
1
Es werden nicht einmal JQuery-Anfragen abgefangen, wenn die JQuery-Instanz anders ist. Änderung des Prototyps bei Bedarf
Shishir Arora
3

Zusätzlich zu der Antwort von meouw musste ich Code in einen Iframe einfügen, der XHR-Aufrufe abfängt, und die obige Antwort verwenden. Ich musste mich jedoch ändern

XMLHttpRequest.prototype.send = function(){

Zu:

XMLHttpRequest.prototype.send = function(body)

Und ich musste mich ändern

oldSend.apply(this, arguments);

Zu:

oldSend.call(this, body);

Dies war erforderlich, damit es in IE9 mit dem IE8-Dokumentmodus funktioniert . Wenn diese Änderung nicht vorgenommen wurde, funktionierten einige vom Komponentenframework (Visual WebGUI) generierte Rückrufe nicht. Weitere Infos unter diesen Links:

Ohne diese Änderungen wurden AJAX-Postbacks nicht beendet.

Walf
quelle