Добавьте «ловушку» ко всем запросам AJAX на странице

104

Я хотел бы знать, можно ли «подключиться» к каждому отдельному запросу AJAX (либо при его отправке, либо при событиях) и выполнить действие. На данный момент я предполагаю, что на странице есть другие сторонние скрипты. Некоторые из них могут использовать jQuery, а другие - нет. Это возможно?

Юлий
источник
Это возможно с jQuery, так что это возможно с простым старым javascript, но вам нужно иметь как минимум 2 «крючка» для каждого из них. В любом случае, зачем использовать оба на одной странице?
yoda
2
Как насчет использования этой библиотеки? github.com/slorber/ajax-interceptor
Себастьен Лорбер,
Это хорошее решение: stackoverflow.com/questions/25335648/…
phazei
2
Примечание. Ответы на этот вопрос не охватывают вызовы Ajax, сделанные из более нового fetch()API, теперь в современных браузерах.
jfriend00
1
@ nirvana-msu - Возможно, это: github.com/werk85/fetch-intercept/blob/develop/src/index.js
jfriend00 02

Ответы:

110

Вдохновленный ответом Авива , я провел небольшое расследование, и это то, что я придумал.
Я не уверен, что все это полезно в соответствии с комментариями в сценарии, и, конечно, будет работать только для браузеров, использующих собственный объект XMLHttpRequest .
Я думаю, что это сработает, если используются библиотеки javascript, поскольку они будут использовать собственный объект, если это возможно.

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
});
мяу
источник
было бы неплохо расширить этот ответ для поддержки хуков после запросов
Себастьян Лорбер
1
На основе вашей реализации я опубликовал в NPM кое-что, что работает как с запросами, так и с ответами! github.com/slorber/ajax-interceptor
Себастьен Лорбер,
4
console.log (xhr.responseText) - пустая строка, потому что в этот момент xhr пуст. если вы передадите переменную xhr в глобальную переменную и установите задержку в несколько секунд, вы сможете получить доступ к свойствам напрямую
Toolkit
5
хороший ответ. Если вы хотите получить данные ответа, проверьте состояние готовности и состояние при изменении состояния. xhr.onreadystatechange = функция () {если (xhr.readyState == 4 && xhr.status == 200) {console.log (JSON.parse (xhr.responseText)); }}
Стэнли Ван
1
@ucheng Если мы добавим onreadystatechange для xhr, будет ли он отменять исходный метод изменения состояния готовности, лучше использовать xhrObj.addEventListener ("load", fn)
Мохамед Хуссейн,
128

ПРИМЕЧАНИЕ. Принятый ответ не дает фактического ответа, поскольку его вызывают слишком рано.

Вы можете сделать это, что в общем будет перехватывать любой AJAX глобально и не испортить какие-либо обратные вызовы и т.д., которые, возможно, были назначены любыми сторонними библиотеками AJAX.

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

Еще несколько документов о том, что вы можете здесь делать с помощью API addEventListener здесь:

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

(Обратите внимание, это не работает <= IE8)

Джон Калвинер
источник
Спасибо! Работает отлично. Я лишь немного модифицирую: this.statusв этом случае возвращаю статус сервера, 200если запрос в порядке, 404, если элемент не найден, 500 и т. Д. Но код работает отлично.
MrMins
1
Это может показаться вам старым, но вы заслуживаете за это всю любовь. Я глубоко застрял. Огромное спасибо.
Carles Alcolea,
Просто потому, что я это немного искал. "load"Событие вызывается только на успех. Если вас не волнует результат (только то, что запрос закончился), вы можете использовать "loadend"событие
Ромуальд Брюне
На это ушло много дней. Спасибо за это гениальное произведение.
Дэвид
Я пробовал много ответов, и это отлично работает с WKWebView. У меня возникли проблемы с использованием событий Ajax, поскольку jQuery может не загружаться до того, как я вызову evalJavascript или использую addUserScript. Спасибо!
Джейк Ченг
21

Так как вы говорите JQuery, я знаю , что Jquery предлагает большой .ajaxSetup()метод , который устанавливает глобальные параметры Ajax , которые включают триггеры событий , как success, errorи beforeSend- что то , что звучит как то , что вы ищете.

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

конечно, вам нужно будет проверить доступность jQuery на любой странице, на которой вы пытаетесь использовать это решение.

Jondavidjohn
источник
1
Спасибо за предложение, но это, к сожалению, не перехватывает вызовы AJAX, которые выполняются без использования AJAX.
Юлий
8
В сегодняшних новостях: Единороги убиты вызовами AJAX, которые не используют AJAX
Дашу
3
он имеет в виду не объект jquery AJAX. Но даже тогда вы сможете каждый раз использовать один и тот же экземпляр jquery
Шишир Арора
1
Очень полезно. Хотел добавить, еще один триггер - statusCode. Подключив что-то вроде statusCode: {403: function () {error msg}, вы можете предоставить глобальную проверку auth / perms / role для всех функций ajax без необходимости перезаписывать один запрос .ajax.
Watercayman
8

Для этого есть хитрость.

Перед запуском всех скриптов возьмите исходный объект XHMHttpReuqest и сохраните его в другом файле var. Затем переопределите исходный XMLHttpRequest и направьте все вызовы к нему через свой собственный объект.

Псевдокод:

 var savd = XMLHttpRequest;
 XMLHttpRequest.prototype = function() {
         this.init = function() {
         }; // your code
         etc' etc'
 };
Авив
источник
10
Это не совсем правильный ответ, если вы измените прототип объекта, даже сохраненный будет изменен. Также весь прототип заменяется одной функцией, которая нарушает все запросы ajax. Однако это вдохновило меня дать ответ.
meouw 05
8

Я нашел хорошую библиотеку на Github, которая хорошо справляется со своей задачей, вы должны включить ее перед любыми другими файлами js.

https://github.com/jpillora/xhook

вот пример, который добавляет заголовок http к любому входящему ответу

xhook.after(function(request, response) {
  response.headers['Foo'] = 'Bar';
});
Мохамед Селим
источник
2
Великий! Но не работал с приложением Cordova даже с последним Chrome-плагином для просмотра веб-страниц!
Islam Attrash
1
Это отлично сработало для моих конкретных потребностей: в Wordpress фильтрация событий из полного календаря плагина Events Organizer
Альфредо Йонг
Не работает для всех запросов, некоторые fetch и xhr в порядке, но, например, он не ловит xhr из google recaptcha
Серджио Кинтеро
@SergioQuintero: я предполагаю, что это ваша проблема с GitHub: github.com/jpillora/xhook/issues/102 . Подумайте об удалении вашего комментария здесь, поскольку это не было проблемой с библиотекой.
30dot
@SergioQuintero Любое переопределение интерфейсов XHR происходит только в этом документе, а не глобально в браузере, потому что это будет огромной дырой в безопасности / конфиденциальности. reCAPTCHA работает в iframe, и вы не можете запустить там переопределение.
Вальф
5

Используя ответ «meouw», я предлагаю использовать следующее решение, если вы хотите увидеть результаты запроса

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 );
    }
});
героин
источник
3

jquery ...

<script>
   $(document).ajaxSuccess(
        function(event, xhr, settings){ 
          alert(xhr.responseText);
        }
   );
</script>
Владимир Мирошниченко
источник
1
jQuery не будет перехватывать запросы, сделанные с использованием других библиотек. Например ExtJS. Если вы используете только jQuery, это хороший ответ. В противном случае он не будет работать все время.
npiani
1
он даже не будет ловить запросы jquery, если экземпляр jquery отличается. При необходимости измените прототип
Шишир Арора
3

В дополнение к ответу meouw мне пришлось ввести код в iframe, который перехватывает вызовы XHR, и использовал приведенный выше ответ. Однако мне пришлось изменить

XMLHttpRequest.prototype.send = function(){

Кому:

XMLHttpRequest.prototype.send = function(body)

И мне пришлось изменить

oldSend.apply(this, arguments);

Кому:

oldSend.call(this, body);

Это было необходимо, чтобы заставить его работать в IE9 с режимом документов IE8 . Если это изменение не было внесено, некоторые обратные вызовы, созданные структурой компонентов (Visual WebGUI), не работали. Больше информации по этим ссылкам:

Без этих изменений обратная передача AJAX не прекратилась.

Вальф
источник