In my question from yesterday, I asked how to retrieve the full HTML content as text. Seems like XHR is the way. Now, however, I have a different problem.
I use this code to retrieve the content of the document as a string:
var req = new XMLHttpRequest();
req.open("GET", document.location.href);
req.onreadystatechange = function () {
if (req.readyState === 4 && (req.status === 200 || req.status == 0)) {
console.log(req.responseText);
}
};
req.send(null);
However, there is a slight delay that I'd like to avoid. I'm testing on localhost and Chrome DevTools says there's several milliseconds of "Stalled" time. In the "Size" column, it says "(from disc cache)", so I know I'm requesting something that the client already has.
Questions
Since the request is about something that already exists, can I somehow make the response instantaneous?
Can I access the original request (the one that is fired after typing the website URL) and access its response text?
My goal is to get the document as a string as soon as possible and without waiting for the DOM to load.
You could implement a cache:
function retrieve(url,callback){
if(localStorage.getItem(url)){
return callback(localStorage.getItem(url));
}
var req = new XMLHttpRequest();
req.open("GET", document.location.href);
req.onreadystatechange = function () {
if (req.readyState === 4 && (req.status === 200 || req.status == 0)) {
localStorage.setItem(url,req.responseText);
callback(req.responseText);
}
};
req.send(null);
}
retrieve("http://test.com",console.log);//takes its time
setTimeout(retrieve,1000,"http://test.com",console.log);//should be very fast...
Also if you want to get the current response, why dont you simply access the document bject?
Related
I am trying to run a conditional statement based on the readystate of my xhr. The readystate switches to 1 after open() is called (line 2), but never changes after that. It skips over the conditionals, and nothing is sent.
I'd love to know why readystate is not changing?
var xhr = new XMLHttpRequest();
xhr.open('POST', submitUrl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
function1();
function2();
} else if (xhr.status === 400) {
function3();
function2();
} else if (xhr.status != 400 && xhr.status != 200) {
function5();
function6();
}
}
xhr.send(body);
})
According to your code, your observations, and the context you've provided in the comments, you've two issues:
sending form data
evaluate the response
Let's assume some basic form like this:
<form action="endpoint.php" method="post">
<input type="hidden" name="token" value="value">
<input type="submit" name="submit" value="submit">
</form>
To be able to send this form's data ourselves, we need to make sure we're intercepting the browser's default behaviour of submitting it right away as soon as the submit button is clicked (cf. also epascarello's comments):
// Intercept the onsubmit event
document.querySelector('form').onsubmit = function (evt) {
// Make sure to prevent the form from being submitted by
// the browser, which is the default behaviour.
evt.preventDefault();
// Get the form's data
let form = new FormData(evt.target);
// We're going to explicitly submitting our data
// as JSON, so make sure it actually is JSON.
let data = JSON.stringify(Object.fromEntries(form)); // https://stackoverflow.com/a/55874235/3323348
sendRequest(data); // Our submit function, which we'll define next (see below)
};
Now, we'd be able to actually send the data, and to properly handle messages and status codes send back by the server. But first, let's have a quick look at your if clauses, because they might not work the way you expect them to. Especially because state and status aren't mutually exclusive - a readyState of 4 doesn't mean the server hasn't answered with an HTTP status code denoting an error (like a 404):
if (xhr.readyState === 4) {
console.log(xhr.status); // Could be any HTTP status code
} else if (xhr.status === 400) {
console.log(xhr.readyState); // Could be any readyState besides 4
} else if (xhr.status != 400 && xhr.status != 200) {
console.log(xhr.readyState); // Could be any readyState besides 4...
console.log(xhr.status); // ...and any HTTP status code besides a Bad Request (400) and an OK (200)
}
So let's tackle that part a bit different, while the rest of your code stays the same (though wrapped in a function):
function sendRequest(data) {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/endpoint.php'); // All requests are asynchronous by default,
// so we can drop the third parameter.
xhr.setRequestHeader('Content-Type', 'application/json');
// Since we've just created a client and initialized
// a request, we'll receive notifications for states
// 2-4 only (instead of 0-4).
xhr.onreadystatechange = function () {
console.log(xhr.readyState); // Let's watch the readyState changing
// We're interested in the final result of our request only (state 4),
// so let's jump all other states.
if (xhr.readyState !== 4) {
return;
}
const status = xhr.status; // HTTP status code
const type = status.toString().charAt(0); // Get class of HTTP status code (4xx, 5xx, ...)
if ([4,5].includes(type)) {
console.log('An error occured', status, xhr.responseText);
return;
}
if (status == 200) {
console.log('OK', xhr.responseText);
return;
}
// Server answered with a status code of 1xx, 3xx, or > 200.
console.log('Unexpected response', status, xhr.responseText);
}
xhr.send(data);
}
Now, you should be able to successfully send form data (and send it as JSON) and evaluate the HTTP response status codes. Instead of using XMLHttpRequest, though, you might want to consider fetch() instead.
Misc:
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#events
https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects
How to convert FormData (HTML5 object) to JSON
Send POST data using XMLHttpRequest
Using the code I found from one of the StackOverflow postings, I'm trying to call a REST service GET method. However, when the code runs it is not putting the GET format correctly in the URL.
Here's the code:
<!DOCTYPE html>
<script>
function UserAction(json)
{
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function ()
{
if (this.readyState == 4 && this.status == 200)
{
alert(this.responseText);
}
};
xhttp.open("GET", "http://localhost:8080/isJsonValid/json", true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.send(json);
}
</script>
<form>
<button type="submit" onclick="UserAction(json)">Check if JSON Valid</button>
<label for="json">JSON:</label>
<input type="text" id="json" name="json"><br><br>
</form>
</html>
The expected format of this GET REST service would be:
http://localhost:8080/isJsonValid/json
(where json in the line above is the actual JSON sent as a parameter.)
Yet, what is shown in the URL line includes the project, directory and the URL has the ?name=value syntax.
Since the GET doesn't match the simple http://localhost:8080/isJsonValid/json format, I get a 404 error.
I realize there's something obvious I'm missing.
Thanks to all for suggestions.
If you need to send data you need to either send it as a query param or as the body. If you want to send it as a body need to use POST type. Below is the example of POST type.
// Create a request variable and assign a new XMLHttpRequest object to it.
var request = new XMLHttpRequest()
// Open a new connection, using the GET request on the URL endpoint
request.open('GET', 'https://ghibliapi.herokuapp.com/films', true)
request.onload = function() {
// Begin accessing JSON data here
var data = JSON.parse(this.response)
if (request.status >= 200 && request.status < 400) {
data.forEach(movie => {
console.log(movie.title)
})
} else {
console.log('error')
}
}
// Send request
request.send()
For post Request. As I don't have any API with me I have used get API URL.
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
console.log(this.responseText)
if (this.readyState == 4 && this.status == 200) {
alert(this.responseText);
}
};
xhttp.open("POST", "https://ghibliapi.herokuapp.com/films", true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.send("Your JSON Data Here");
Thanks all for the great input and help!
The best solution for me was to just use, as suggested, a POST. The GET was always putting the "?" in the URL even if I concatenated it, That "?" isn't how the REST service interprets the GET parameters so it wouldn't work that way. In the REST framework I'm using, GET parameters are just concatenated with one or more "/" as separators in the URL.
Appreciate all the terrific help here on SO. :)
I'm running a Heroku server and I'm trying to change the innerHTML on my website to display certain statistics gathered from an endpoint on the server:
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.send();
xhr.onreadystatechange = processRequest;
var element = document.getElementById('change');
element.innerHTML = xhr.valueinJSON;
function processRequest(e) {
if (xhr.readyState == 4 && xhr.status == 200) {
var response = JSON.parse(xhr.responseText);
alert(response.ip);
}
}
My script tag contains the above and although the endpoint works properly in Postman, the JSON is always null. Any suggestions?
I'm not sure if valueinJSON is from an API I'm not familiar with, but I can't find a reference to it. Either way, you need to change your innerHTML in the async function like this:
if (xhr.readyState == 4 && xhr.status == 200) {
var response = JSON.parse(xhr.responseText);
var element = document.getElementById('change');
element.innerHTML = response
}
You are trying to set the innerHTML before the browser has a chance to download it.
This assumes there is not a CORS issue. You should check your browser's console to make sure there are not errors.
I use the code below to get a web page(html)
var htmlString=null;
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.yahoo.com");//can change to any web address
xhr.onreadystatechange = function() {
htmlString=htmlString+xhr.responseText;
if(xhr.statusText=="200 OK\r" ){
log (global.htmlString.length);
}
}
but it always get one part of the page, rather than whole html code
Is there any parameter to set the length of the return html code?
Your comment welcome
There will be multiple readystatechange events. The request will only be completely done when xhr.readyState === XMLHttpRequest.DONE === 4.
Also, htmlString = null; ...; htmlString=htmlString+xhr.responseText; is bogus in many ways. First time around it will do htmlString = null + "text" === "nulltext. Afterwards it will add .responseText (as retrieved so far) again and again, while going through the states.
Also, on a related note, you should check xhr.status == 200, not xhr.statusText == randomString. Web servers aren't necessarily sending "OK" in case of 200.
Read the XMLHttpRequest documentation.
Something like this should work better:
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.yahoo.com");
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 /* DONE */) {
console.log(xhr.responseText.length);
// Do something with it...
}
}
xhr.send();
I'm doing an AJAX fetch of a binary file the I am parsing in javascript. (Quake 2 BSPs, if anyone cares.) The code to fetch and parse the initial file is working fine, and looks roughly like this:
function loadFile(url) {
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (request.readyState == 4 && request.status == 200) {
var parsed = parseFile(request.responseText);
}
};
request.open('GET', url, true);
request.overrideMimeType('text/plain; charset=x-user-defined');
request.setRequestHeader('Content-Type', 'text/plain');
request.send(null);
}
As I said, that works fine, and everything loads and parses correctly. However, the file also describes several secondary files (textures) that need to be retrieved as well, and so I've added an inner loop that should load and parse all of those files, like so:
function loadFile(url) {
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (request.readyState == 4 && request.status == 200) {
var parsed = parseFile(request.responseText);
for(var i = 0; i < parsed.files.length; ++i) {
loadSecondaryFile(parsed.files[i].url); // Request code here is identical to this function
}
}
};
request.open('GET', url, true);
request.overrideMimeType('text/plain; charset=x-user-defined');
request.setRequestHeader('Content-Type', 'text/plain');
request.send(null);
}
function loadSecondaryFile(url) {
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (request.readyState == 4 && request.status == 200) {
var parsed = parseSecondaryFile(request.responseText);
}
};
request.open('GET', url, true);
request.overrideMimeType('text/plain; charset=x-user-defined');
request.setRequestHeader('Content-Type', 'text/plain');
request.send(null);
}
But every request made from within that loop immediately fails with the message (in Chrome, Dev Channel): NETWORK_ERR: XMLHttpRequest Exception 101 This strikes me as strange, since if I call loadSecondaryFile outside of loadFile it works perfectly.
My initial impression was that initiating an one ajax call in the onreadystatechage of another may be bad juju, but wrapping the secondary ajax calls in a setTimer doesn't make any difference.
Any ideas?
And... SUCCESS! So I feel really stupid, and I realize now that there's no way anyone else could have given me a solution with the information I presented. Terribly sorry!
It has nothing to do with AJAX and everything to do with how I was getting my URLs. Recall that I mentioned I was loading binary data from a Quake2 bsp, in this case, texture paths. Textures in the bsp format are stored as fixed length 32 bit strings with null padding. I was reading them using substr like so:
var path = fileBuffer.substr(fileOffset, 32);
Which I thought was giving me a string like "e2u3/clip", but in reality was giving me "e2u3/clip\0\0\0\0..." Of course, when printed this would look correct (since console.log represents the null char as nothing.) but the browser recognized it immediately as a bad URL and tossed it out.
Changing my read code to:
var path = fileBuffer.substr(fileOffset, 32).replace(/\0+$/,'');
Gives me valid strings and fixes all of my apparent AJAX problems! sigh
Thanks for all the suggestions! It helped put me on the right track.