JavaScript: Detect AJAX requests - javascript

Is there any way to detect global AJAX calls (particularly responses) on a web page with generic JavaScript (not with frameworks)?
I've already reviewed the question "JavaScript detect an AJAX event", here on StackOverflow, and tried patching in the accepted answer's code into my application but it didn't work. I've never done anything with AJAX before either so, I don't know enough to modify it to work.
I don't need anything fancy, I just need to detect all (specific, actually, but I'd have to detect all first and go from there) AJAX responses and patch them into an IF statement for use. So, eventually, I'd like something like:
if (ajax.response == "certainResponseType"){
//Code
}
, for example.
Update:
It seems I should clarify that I'm not trying to send a request - I'm developing a content script and I need to be able to detect the web page's AJAX requests (not make my own), so I can execute a function when a response is detected.

Here's some code (tested by pasting into Chrome 31.0.1650.63's console) for catching and logging or otherwise processing ajax requests and their responses:
(function() {
var proxied = window.XMLHttpRequest.prototype.send;
window.XMLHttpRequest.prototype.send = function() {
console.log( arguments );
//Here is where you can add any code to process the request.
//If you want to pass the Ajax request object, pass the 'pointer' below
var pointer = this
var intervalId = window.setInterval(function(){
if(pointer.readyState != 4){
return;
}
console.log( pointer.responseText );
//Here is where you can add any code to process the response.
//If you want to pass the Ajax request object, pass the 'pointer' below
clearInterval(intervalId);
}, 1);//I found a delay of 1 to be sufficient, modify it as you need.
return proxied.apply(this, [].slice.call(arguments));
};
})();
This code solves the above issue with the accepted answer:
Note that it may not work if you use frameworks (like jQuery), because
they may override onreadystatechange after calling send (I think
jQuery does). Or they can override send method (but this is unlikely).
So it is a partial solution.
Because it does not rely on the 'onreadystatechange' callback being un-changed, but monitors the 'readyState' itself.
I adapted the answer from here: https://stackoverflow.com/a/7778218/1153227

Gives this a try. Detects Ajax responses, then I added a conditional using the XMLHttpRequest propoerties readyState & status to run function if response status = OK
var oldXHR = window.XMLHttpRequest;
function newXHR() {
var realXHR = new oldXHR();
realXHR.addEventListener("readystatechange", function() {
if(realXHR.readyState==4 && realXHR.status==200){
afterAjaxComplete() //run your code here
}
}, false);
return realXHR;
}
window.XMLHttpRequest = newXHR;
Modified from:
Monitor all JavaScript events in the browser console

This can be a bit tricky. How about this?
var _send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
/* Wrap onreadystaechange callback */
var callback = this.onreadystatechange;
this.onreadystatechange = function() {
if (this.readyState == 4) {
/* We are in response; do something,
like logging or anything you want */
}
callback.apply(this, arguments);
}
_send.apply(this, arguments);
}
I didn't test it, but it looks more or less fine.
Note that it may not work if you use frameworks (like jQuery), because they may override onreadystatechange after calling send (I think jQuery does). Or they can override send method (but this is unlikely). So it is a partial solution.
EDIT: Nowadays (the begining of 2018) this gets more complicated with the new fetch API. Global fetch function has to be overridden as well in a similar manner.

A modern (as of April 2021) answer to the question is to use PerformanceObserver which lets you observe both XMLHttpRequest requests and fetch() requests:
Detect fetch API request on web page in JavaScript
Detect ajax requests from raw HTML
<!-- Place this at the top of your page's <head>: -->
<script type="text/javascript">
var myRequestLog = []; // Using `var` (instead of `let` or `const`) so it creates an implicit property on the (global) `window` object so you can easily access this log from anywhere just by using `window.myRequestLog[...]`.
function onRequestsObserved( batch ) {
myRequestLog.push( ...batch.getEntries() );
}
var requestObserver = new PerformanceObserver( onRequestsObserved );
requestObserver.observe( { type: 'resource' /*, buffered: true */ } );
</script>
I use the above snippet in my pages to log requests so I can report them back to the mothership in my global window.addEventListenr('error', ... ) callback.
The batch.getEntries() function returns an array of DOM PerformanceResourceTiming objects (because we're only listening to type: 'resource', otherwise it would return an array of differently-typed objects).
Each PerformanceResourceTiming object has useful properties like:
The initiatorType property can be:
A HTML element name (tag name) if the request was caused by an element:
'link' - Request was from a <link> element in the page.
'script' - Request was to load a <script>.
'img' - Request was to load an <img /> element.
etc
'xmlhttprequest' - Request was caused by a XMLHttpRequest invocation.
'fetch' - Request was caused by a fetch() call.
name - The URI of the resource/request. (If there's a redirection I'm unsure if this is the original request URI or the final request URI).
startTime: Caution: this is actually the time since PerformanceObserver.observe() was called when the request was started.
duration: Caution: this is actually the time since PerformanceObserver.observe() was called when the request completed: it is not the duration of the request alone. To get the "real" duration you need to subtract startTime from duration.
transferSize: the number of bytes in the response.

Related

How to make requests with xmlhttprequest until response is ready?

I'm trying to make a request to a route which makes a query to an API and if the API has the data, the response is to render another website with the API data. But if the data is not ready yet, since it is still processing, the route returns a string "not finished yet".
What I wish to do is: make a get request and if the response is "not finished yet" wait for 5 seconds and do the request again until the response is the data. After it, the script would open the window with the new page with the data loaded.
Here is what I have already made:
job_id = document.querySelector("#job_id").getAttribute("value")
code = document.querySelector("#code").getAttribute("value")
​
var myRequest = new XMLHttpRequest();
​myRequest.open('GET', `http://127.0.0.1:5000/status/${job_id}/${code}`);
​myRequest.onreadystatechange = function () {
if (myRequest.readyState === 4 && myRequest.responseText != 'not finished yet') {
window.location = `http://127.0.0.1:5000/status/${job_id}/${code}`
}
};
If anyone knows if it works or knows a better way to deal with that, I'd appreciate your help.
Thanks in advance.
Solution:
After some hours, I finnaly found a way to handle it. Still don't know if it is the best way.
function search() {
job_id = document.querySelector("#job_id").getAttribute("value")
code = document.querySelector("#code").getAttribute("value")
var myRequest = new XMLHttpRequest();
myRequest.open('GET', `http://127.0.0.1:5000/status/${job_id}/${code}`);
myRequest.send();
myRequest.onreadystatechange = function () {
if (myRequest.readyState === 4 && myRequest.responseText === 'not finished yet') { setTimeout(function () {search();}, 5000)
}
else if(myRequest.readyState === 4 && myRequest.responseText != 'not finished yet')
{ window.location = `http://127.0.0.1:5000/status/${job_id}/${code}`}}
}
search()
I use var option = {}; as a global object to handle OOP (object oriented programming).
What you want to do is when you need to define something give it a prefix for the function and an identifier so you can avoid conflicts.
You posted some code so at 1 reputation and decent formatting you're doing a lot better than most starting at 1, kudos. Let's say you're working with a job ID of 79. So you'll want to define the following:
option.job_79 = 1;
Now I assigned the sub-object a 1 as a status, it's initialized. Since the option object is global scope you can have another call to your ajax() function and without it knowing that another ajax() function is already running you simply check for the typeof option.job_79 instead!
Some recommendations:
If you're enthusiastic about programming you'll eventually want to merge all your AJAX functions in to one single well refined function, it'll not only greatly simplify your code the up-front cost will save you and the earlier the better (though the more you'll have to refine it over time).
Also avoid the evils of frameworks and libraries. People make such a big deal about them but a few years later when you want to update you can't without spending days or weeks refactoring code. I've never had to refactor code using only pure JavaScript for any other reason other than my experience level, never because of a browser update. There are numerous other benefits that are hidden along that path and most people aren't aware of that.

Abort Xhr on cujojs rest.js

I'm using cujojs/rest to send requests to my (laravel) API. I'm looking for a way to cancel requests that are not over when a new one comes in.
There is a cancel method on the client which may be what I need to solve my problem :
My app displays a paginated collection of items, people can browse pages by clicking a "next/prev" button.
If the user clicks multiple times on the 'next' button quickly every new page will fire of a new request. I'd like to make sure that only the latest request gets going and all the other (unfinished) ones are aborted.
I used to do this with a beforeSend method when I was using another tool to perform my requests. It would add the xhr object to an array and before a request was fired it would call .abort() on all the xhr in that array if they were not over.
Now that I switched to cujojs/rest, I can't figure out how the cancel method could be used to accomplish that.
Of course I don't want to abort any request before a new one is run, just the ones that tap the same resource as I might have unrelated data loading elsewhere.
/users?page=1 -> should be canceled
/users?page=2 -> should be canceled
/preferences -> should NOT be canceled
/users?page=3 -> should be canceled
/users?page=4 -> should go through as its the last one
Any help would be very appreciated.
I am not sure because there is no proper documentation as far as i can find. But judging from the source code you should be able to do something like this
//lets say you defined **this.request** somewhere in the constructor to hold the request value which defaults to empty object {};
fetchCollection: function(id) {
var that = this;
var url = 'collections/' + id;
if( Object.keys( that.request ).length !== 0 ) {
if( 'function' === typeof that.request.cancel )
that.request.cancel();
else
that.request.canceled = true;
//here we should re-assign the default {} value again
that.request = {};
}
that.request.path = url;
return client( that.request ).then(
function(response){
that.collection = response.entity.data;
},
function(response){
console.error(response.status.code, response.status);
}
);
},
If it works i can explain why.
Ok so now when it works i can explain why using pseudo-code:
Inside client( request ) function cujojs before doing anything does this:
Checks if request.canceled exists and is equal to true
Aborts request if condition is met
If request.canceled doesn't exist it continues and goes to next step
cujojs defines request.cancel function which can abort the request before it is sent ( we don't care about steps after because cancelation is our intention )
Now what we are doing is we are using javascripts innate ability to pass variables by reference. By reference means that when you are giving create() function a variable request the function uses this same variable inside of itself, instead of creating a brand new copy of the variable request and using that copy.
The way we are exploiting this here is that we are manipulating request variable from outside of cujojs knowing that it is using the same variable thus our manipulation will directly affect request variable cujojs is using at the time of execution create() function.
Now that we know that cujojs has 2 methods of canceling request, and that we can manipulate request variable after create() function received it we do a simple checking on our side:
We check if this.request is empty ( it will only be empty if no request was sent yet )
If it is not empty we check whether cujojs already has defined the .cancel function on our request variable by doing 'function' === typeof request.cancel
if it had we can use this function to cancel the request we sent previously,
if it hadn't we know that cujojs is for now on the step which checks .canceled variable on our request variable so we assign this to true doing this.request.canceled = true;
After canceling the request, we assign request brand new value {} thus losing the reference to our previous request variable which we manipulated earlier
I am very poor at explaining things but i hope you understood, knowing such nuances will help you a lot in your development.
Happy coding

How to work with 2 XMLHttpRequest one dependent on another?

I am working on a project where I have got 2 XMLHttpRequest() objects, say A and B.
What I want to accomplish is when A finish fetching a list of data items, B will be triggered to fetch some more items based on the previous data items fetch by A.
Currently my problem is that the two objects are working independent of one another.
My code is below:
var A = new XMLHttpRequest();
var B = new XMLHttpRequest();
A.open("GET", directory, true);
A.onreadystatechange = function () {
if (A.readyState === 4) {
if (A.status === 200 || A.status == 0) {
//does... something
}
}
}
A.send(null);
while(true){
B.open("GET", another_directory, false);
B.overrideMimeType("application/document");
B.send(null);
if (B.status == "404")
continue;
//does... something else
}
This code is not working because I find evertime B proceed before A can complete. I basically don't know which event to use.
How can I accomplish my objective?
What events can I use so that I can sync processing B right after finishing with A?
Ok, so let's start with your code. I've added a few comments to it, so now you can understand the source of the problem:
var A = new XMLHttpRequest(); //You create an XMLHttpRequest object
var B = new XMLHttpRequest(); //And an another
A.open("GET", directory, true);
/* Now you open a GET request to DIRECTORY, with async TRUE. The third parameter can
make a request sync or async, but sync is not recommended as described below. */
A.onreadystatechange = function () {
if (A.readyState === 4) {
if (A.status === 200 || A.status == 0) {
/* So you registered an event listener. It runs when the readyState changes.
You can use it to detect if the request is finished or not. If the readyState is
4, then the request is finished, if the status code is 200, then the response is
OK. Here you can do everythin you want after the request. */
}
}
}
A.send(null); //Now you send the request. When it finishes, the event handler will
// do the processing, but the execution won't stop here, it immediately goes to the
// next function
while(true){ // Infinite loop
B.open("GET", another_directory, false); //Open request B to ANOTHER_DIRECTORY,
// but now, request B will be synchronous
B.overrideMimeType("application/document"); // Configure mime type
B.send(null); // Send the request
if (B.status == "404")
continue;
// If it's not found, then go to the next iteration
// and do something else
}
I hope that now you can see the source of the problem. When you run this script, then your start an async request and then immediately start the next one. Now you can choose from 2 ways.
Run next request from callback (recommended)
It's the better way. So start your first (async) request and in the event listener (where you do the processing) you can start the next request. I've made a commented example here: http://jsfiddle.net/5pt6j1mo/1/
(You can do it without arrays - it was just an example)
If you use this way then the GUI won't freeze until you are waiting for response. Everything will be responsible so you can interact with the page, you can create cancel button, etc.
Synchronous AJAX (not recommended)
I don't recommend it because "Synchronous XMLHttpRequest on the main thread is deprecated" in Chrome, but if you really want to then you can try to use this solution. So an XMLHttpRequest's open function has 3 arguments:
METHOD: which HTTP methid to use
URL: which URL to request
ASYNC: Asynchronous request? If false then it will be synchronous wich means that after you call .send(), it will pause execution until the response comes back.
So if you set the third parameter to FALSE then you can easily do it... but you shouldn't!
Here is an alternative solution, either use the fetch API or promisify native XHR and this problem becomes much simpler:
fetch(directory).then(function(response){
// some processing
return fetch(another_directory); // can change content type too, see the mdn docs
}).then(function(responseTwo){
// all processing is done
}).catch(function(err){
// handle errors from all the steps above at once
});
This is just as native as XHR, and is much much simpler to manage with promises.
(After lengthy edit) I'd recommend strongly that you take the time to understand the nature of asynchronous calls within JavaScript. Here's a bit of recommended reading.Asynchronous Programming in JavaScript I think that is simple enough to understand what is going on. Note: Stop reading at "Enter Mobl".
In JavaScript when you call a function, the system places that function into a 'queue' with an implicit instruction to go ahead and run it as soon as you can. It does that for each and every function call. In your case you are telling the system to run A, then run B. A goes in the queue, B goes in the queue. They are submitted as individual functions. B happens to run first.
For normal functions, if you want to control the sequence, you can nest the A function call within the B function call. But oops. You are using XMLHttpRequest, so that limits your ability to customize the functions. Read on. Check out Ajax Patterns on the subject Look at the paragraph for "Asynchronous Calls". Look at your code...
A.onreadystatechange = function () {
if (A.readyState === 4) {
if (A.status === 200 || A.status == 0) {
//does... something
(RUN ALL THE B.methods right here...)
}
}
}
I think that will get you to your destination, assuming you want a no jQuery solution.
For the person who just wants a functioning system, and doesn't want to understand the language better, here is a jquery solution... Note how the B function call is nested within the A function call. Do note that the order of this nesting is based on the presence of the jQuery success tag. If not using jQuery, you will manually have to nest the functions as appropriate.
var special_value;
$("button").click(function(){
$.ajax({url: "demo_testA.html",
type: 'GET',
success: function(resultA){
special_value = resultA;
$.ajax({url: "demo_testB.html",
type: 'GET',
data: special_value,
success: function(resultB){
$("#div1").html(resultB);
}});
});
});
I will say, it would be much easier to help you help yourself with the use of better communications. If you don't like something, then so state. If you don't understand something ask for more clarification or edit your problem statement. Feedback is a good thing.

Internals (client and server) of aborting an XMLHttpRequest

So I'm curious about the actual underlying behaviours that occur when aborting an async javascript request. There was some related info in this question but I've yet to find anything comprehensive.
My assumption has always been that aborting the request causes the browser to close the connection and stop processing it entirely, thus causing the server to do the same if it's been setup to do so. I imagine however that there might be browser-specific quirks or edge cases here I'm not thinking of.
My understanding is as follows, I'm hoping someone can correct it if necessary and that this can be a good reference for others going forwards.
Aborting the XHR request clientside causes the browser to internally close the socket and stop processing it. I would expect this behaviour rather than simply ignoring the data coming in and wasting memory. I'm not betting on IE on that though.
An aborted request on the server would be up to what's running there:
I know with PHP the default behaviour is to stop processing when the client socket is closed, unless ignore_user_abort() has been called. So closing XHR connections saves you server power as well.
I'm really interested to know how this could be handled in node.js, I assume some manual work would be needed there.
I have no idea really about other server languages / frameworks and how they behave but if anyone wants to contribute specifics I'm happy to add them here.
For the client, the best place to look is in the source, so let's do this! :)
Let's look at Blink's implementation of XMLHttpRequest's abort method (lines 1083-1119 in XMLHttpRequest.cpp):
void XMLHttpRequest::abort()
{
WTF_LOG(Network, "XMLHttpRequest %p abort()", this);
// internalAbort() clears |m_loader|. Compute |sendFlag| now.
//
// |sendFlag| corresponds to "the send() flag" defined in the XHR spec.
//
// |sendFlag| is only set when we have an active, asynchronous loader.
// Don't use it as "the send() flag" when the XHR is in sync mode.
bool sendFlag = m_loader;
// internalAbort() clears the response. Save the data needed for
// dispatching ProgressEvents.
long long expectedLength = m_response.expectedContentLength();
long long receivedLength = m_receivedLength;
if (!internalAbort())
return;
// The script never gets any chance to call abort() on a sync XHR between
// send() call and transition to the DONE state. It's because a sync XHR
// doesn't dispatch any event between them. So, if |m_async| is false, we
// can skip the "request error steps" (defined in the XHR spec) without any
// state check.
//
// FIXME: It's possible open() is invoked in internalAbort() and |m_async|
// becomes true by that. We should implement more reliable treatment for
// nested method invocations at some point.
if (m_async) {
if ((m_state == OPENED && sendFlag) || m_state == HEADERS_RECEIVED || m_state == LOADING) {
ASSERT(!m_loader);
handleRequestError(0, EventTypeNames::abort, receivedLength, expectedLength);
}
}
m_state = UNSENT;
}
So from this, it looks like the majority of the grunt work is done within internalAbort, which looks like this:
bool XMLHttpRequest::internalAbort()
{
m_error = true;
if (m_responseDocumentParser && !m_responseDocumentParser->isStopped())
m_responseDocumentParser->stopParsing();
clearVariablesForLoading();
InspectorInstrumentation::didFailXHRLoading(executionContext(), this, this);
if (m_responseLegacyStream && m_state != DONE)
m_responseLegacyStream->abort();
if (m_responseStream) {
// When the stream is already closed (including canceled from the
// user), |error| does nothing.
// FIXME: Create a more specific error.
m_responseStream->error(DOMException::create(!m_async && m_exceptionCode ? m_exceptionCode : AbortError, "XMLHttpRequest::abort"));
}
clearResponse();
clearRequest();
if (!m_loader)
return true;
// Cancelling the ThreadableLoader m_loader may result in calling
// window.onload synchronously. If such an onload handler contains open()
// call on the same XMLHttpRequest object, reentry happens.
//
// If, window.onload contains open() and send(), m_loader will be set to
// non 0 value. So, we cannot continue the outer open(). In such case,
// just abort the outer open() by returning false.
RefPtr<ThreadableLoader> loader = m_loader.release();
loader->cancel();
// If abort() called internalAbort() and a nested open() ended up
// clearing the error flag, but didn't send(), make sure the error
// flag is still set.
bool newLoadStarted = m_loader;
if (!newLoadStarted)
m_error = true;
return !newLoadStarted;
}
I'm no C++ expert but from the looks of it, internalAbort does a few things:
Stops any processing it's currently doing on a given incoming response
Clears out any internal XHR state associated with the request/response
Tells the inspector to report that the XHR failed (this is really interesting! I bet it's where those nice console messages originate)
Closes either the "legacy" version of a response stream, or the modern version of the response stream (this is probably the most interesting part pertaining to your question)
Deals with some threading issues to ensure the error is propagated properly (thanks, comments).
After doing a lot of digging around, I came across an interesting function within HttpResponseBodyDrainer (lines 110-124) called Finish which to me looks like something that would eventually be called when a request is cancelled:
void HttpResponseBodyDrainer::Finish(int result) {
DCHECK_NE(ERR_IO_PENDING, result);
if (session_)
session_->RemoveResponseDrainer(this);
if (result < 0) {
stream_->Close(true /* no keep-alive */);
} else {
DCHECK_EQ(OK, result);
stream_->Close(false /* keep-alive */);
}
delete this;
}
It turns out that stream_->Close, at least in the BasicHttpStream, delegates to the HttpStreamParser::Close, which, when given a non-reusable flag (which does seem to happen when the request is aborted, as seen in HttpResponseDrainer), does close the socket:
void HttpStreamParser::Close(bool not_reusable) {
if (not_reusable && connection_->socket())
connection_->socket()->Disconnect();
connection_->Reset();
}
So, in terms of what happens on the client, at least in the case of Chrome, it looks like your initial intuitions were correct as far as I can tell :) seems like most of the quirks and edge cases have to do with scheduling/event notification/threading issues, as well as browser-specific handling, e.g. reporting the aborted XHR to the devtools console.
In terms of the server, in the case of NodeJS you'd want to listen for the 'close' event on the http response object. Here's a simple example:
'use strict';
var http = require('http');
var server = http.createServer(function(req, res) {
res.on('close', console.error.bind(console, 'Connection terminated before response could be sent!'));
setTimeout(res.end.bind(res, 'yo'), 2000);
});
server.listen(8080);
Try running that and canceling the request before it completes. You'll see an error at your console.
Hope you found this useful. Digging through the Chromium/Blink source was a lot of fun :)

Ignoring old multiple asynchronous ajax requests

I've got a custom javascript autocomplete script that hits the server with multiple asynchronous ajax requests. (Everytime a key gets pressed.)
I've noticed that sometimes an earlier ajax request will be returned after a later requests, which messes things up.
The way I handle this now is I have a counter that increments for each ajax request. Requests that come back with a lower count get ignored.
I'm wondering: Is this proper? Or is there a better way of dealing with this issue?
Thanks in advance,
Travis
You can store a "global" currentAjaxRequest, which holds the structure of the last XHR request. Then you can abort the current request when you make a new one.
For example:
var currentAjaxRequest = null;
function autoCompleteStuff() {
if(currentAjaxRequest !== null) {
currentAjaxRequest.abort();
}
currentAjaxRequest = $.get(..., function(...) {
currentAjaxRequest = null;
...
});
}
To avoid naming conflicts, wrap that in an anonymous, instantly-executed function, if needed.

Categories