onload equivalent to onreadystatechange (readyState: 4, status: 200) - javascript

Is onload equivalent to onreadystatechange with readyState 4, status 200?
This SO post says yes
But if they are equivalent, then why does this MDN article nest the two as such:
var xhr = new XMLHttpRequest();
xhr.open("GET", "/bar/foo.txt", true);
xhr.onload = function (e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error(xhr.statusText);
}
}
};
xhr.onerror = function (e) {
console.error(xhr.statusText);
};
xhr.send(null);
[EDIT] Not duplicate of:
Is onload equal to readyState==4 in XMLHttpRequest?
The second answer in the above post explains that it is not exactly the same as readyState === 4. However, if you include status === 200 you have addressed the difference he/she talks about. My OP shows status nested inside readyState.

Related

How to make callback run asynchronously?

I have a JavaScript function called getAandB which takes a callback. getAandB firstly gets value 'a' using ajax. It then invokes the callback with value 'a' as an argument. The callback gets value 'b' and console.logs both 'a' and 'b' to the console. so I get {"key":"a"} {"key":"b"} in the console.
I thought that the two ajax calls would happen simultaneously / asynchronously. However, they seem to run one after the other ie. synchronously.
The JavaScript code and the PHP code for the ajax requests is shown below:
index.html:
<script>
function getAandB(callback){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-a.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
callback(xhr.responseText)
}
}
xhr.send();
}
function callback(resultA){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-b.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
const resultB = xhr.responseText;
console.log(resultA, resultB);
}
}
xhr.send();
}
getAandB(callback);
</script>
ajax-a.php:
<?php
sleep(5);
$response = [
"key" => "a",
];
echo json_encode($response);
The code for ajax-b.php is the same as for ajax-a.php except the value of $response.key is b not a.
I thought that the above code would result in ajax calls being made simultaneously to get 'a' and 'b'. However if the PHP code sleeps for 5 seconds for both ajax-a.php and ajax-b.php, then it takes 10 seconds for the console.log to appear. If only one of the ajax-?.php scripts sleeps for 5 seconds then it takes 5 seconds for the console.log to appear.
How can I use callbacks to allow me to combine the results of ajax calls, as I have done here, but to make the individual calls happen simultaneously / asynchronously? Alternatively, is not possible to implement this with callbacks?
If you want the request to ajax-b to me made at approximately the same time as the request for ajax-a then you need to make the respective calls to xhr.send() at approximately the same time.
At the moment, the call to ajax-b's send() takes place as part of callback() which you only call after you have received the response to the request for ajax-a.
You then need to add additional logic to determine when you have received both responses so you log both bits of data at the same time (assuming you still want to do that).
A rough and ready way to do that, keeping to your current approach, would look something like this:
function getA(callback){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-a.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
callback(xhr.responseText)
}
}
xhr.send();
}
function getB(callback){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-b.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
const resultB = xhr.responseText;
callback(xhr.responseText)
}
}
xhr.send();
}
function getAandB() {
const data = [];
function callback(responseData) {
data.push(responseData);
if (data.length === 2) {
console.log(...data);
}
}
getA(callback);
getB(callback);
}
getAandB();
We have better tools for that these days though, thanks to promises and modern APIs (like fetch) which support them natively.
async function getAandB() {
const dataPromises = [
fetch("./ajax-a.php").then(r => r.text()),
fetch("./ajax-b.php").then(r => r.text())
];
const data = await Promise.all(dataPromises);
console.log(...data);
}
getAandB();
I tried to edit my question but 'the edit queue was full'.
It took me a while to understand #Quentin's answer but I finally realized it relies on the fact that both instantiations of the callback function are altering the same variable (I think that is called by reference and is the default situation with arrays). Given this, although the instantiations know nothing about each other, it is possible to know when both ajax calls have completed by checking to see if the data array has been updated twice. If it has then both must have completed and data can be consoled out.
There is no need for the getAandB function. This much simpler and less confusing code works exactly the same as Quentin's answer:
<script>
const data = [];
function getA(){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-a.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
data.push(xhr.responseText);
if (data.length === 2){
console.log(...data);
}
}
}
xhr.send();
}
function getB(){
const xhr = new XMLHttpRequest();
xhr.open('GET', './ajax-b.php', true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
data.push(xhr.responseText);
if (data.length === 2){
console.log(...data);
}
}
}
xhr.send();
}
getA();
getB();
</script>

Why Promise return an empty string?

I have a function foo which makes an Ajax request.
I tried returning the data from the callback and got the data with "xhr.onload" successfully but got an empty string("") with "xhr.onreadystatechange".
Could anyone tell me why??
Thank you very much!
function foo(url){
return new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open("GET",url,true);
// xhr.onload = function(){
// if(xhr.status == 200){
// resolve(xhr.responseText);
// }else{
// reject("false")
// }
// }
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState && xhr.status == 200){
resolve(xhr.responseText);
}else{
reject("false")
}
}
})
}
foo(url).then(function (data){
console.log(data)
},function (err){
console.log(err)
})
Your onreadystatechange handler is incorrect. You need to check readyState for the value 4 (not just any truthy value), and you don't want to reject until readyState is 4:
if(xhr.readyState === 4){
if (xhr.status == 200) { // or xhr.status >= 200 && xhr.status < 300
resolve(xhr.responseText);
} else {
reject("false")
}
}
But with modern browsers, you'd probably use fetch instead, which already provides a promise. Just be sure not to make these common mistakes (that's a post on my anemic little blog).
As for why you were seeing what you were seeing, since you called open and send before attaching the handler apparently you didn't get the callback for readyState 1 (opened), so it looks like the first callback you got was when the headers were received (readyState 2), at which point xhr.status would be set — so you were resolving your promise, but of course, the request body hadn't been received yet.

Get XMLHttpRequest respond for every data received

I'm trying to make a script that will return a respond when data is received on the current page. (New Data Received > Log its content to the console)
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(xhr.responseText);
}
}
xhr.prototype.open = (function(fopen){
return function(){
console.log("Data received.");
return fopen.apply(this,arguments);
}
})(XMLHttpRequest.prototype.open);
The above script is this script. (source)
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(xhr.responseText);
}
}
xhr.open('GET', 'http://example.com', true);
xhr.send(null);
Combine with this script. (source)
XMLHttpRequest.prototype.open = (function(fopen){
return function(){
console.log("Data received.");
return fopen.apply(this,arguments);
}
})(XMLHttpRequest.prototype.open)
I wanted to know what I did wrong and how to make it work. Thanks!
You're assigning to the prototype property of your instantiated object, not to the prototype of XMLHttpRequest. You might want to change XMLHttpRequest.prototype.onreadystatechange instead:
Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', {
set: function(listenerFn) {
this.addEventListener('readystatechange', function(...handlerArgs) {
if (this.readyState == XMLHttpRequest.DONE) {
// Custom action:
// console.log(xhr.responseText);
console.log('Detected a new response');
// Run the previous callback that was passed in:
listenerFn.apply(this, handlerArgs);
}
});
}
});
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://stacksnippets.net');
xhr.onreadystatechange = () => console.log('handler running');
xhr.send();
This isn't completely in line with the spec of course, this is just an example monkeypatch one might start with. (Mutating the built-in objects like XMLHttpRequest.prototype is bad practice, though - consider avoiding it if possible)
This will log all xhr request
XMLHttpRequest.prototype.send = (function(fsend){
return function(){
this.addEventListener('load', function(){
console.log("Data received.", this.response)
}, {once: true})
return fsend.apply(this, arguments);
}
})(XMLHttpRequest.prototype.send);
xhr = new XMLHttpRequest
xhr.open('GET', 'https://httpbin.org/get')
xhr.send()

XHR calls return empty

I am new to AJAX and I am trying to make a call to my json file but it is not returning anything in console. I looked into network tab, the xhr status is 200.
const xhr = new XMLHttpRequest();
xhr.readyState = function(){
if(xhr.readyState === 4){
console.log(xhr.responseText)
}
}
xhr.open('GET','data/task.json');
xhr.send();
and task.json is
{
"jobs": [
{
"id": 7,
"title": "Clerk",
"employer": {
"name": "AOL"
},
"location": "Floria",
"salary": "$45k+"
}
]
}
I tried parsing it and using console to print it out but the console is empty as well.
There a few things to notice in the request that you are making.
You need to add the status to 200 because it's the OK status. Meaning that your request go through. So that part will exactly become (this.readyState == 4 && this.status == 200).
You also need to change from using triple equal signs === to == because you are not comparing by TYPE as a triple equals is used in JS.
Using the event handler xhttp.onreadystatechange instead of xmr.readySate. Here is a link to the Mozilla documentation.
So it should become:
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange= function(){
if(xhttp.readyState === 4 && xhttp.status == 200){
console.log(xhr.responseText)
}
}
xhr.open('GET','data/task.json');
xhr.send();
Here is a detailed documentation from W3Schools Documentation with example.
You need onreadystatechange not readyState
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
console.log(xhr.responseText)
}
}
xhr.open('GET','data/task.json');
xhr.send();
Instead of xhr.readyState you should use xhr.onreadystatechange
like that:
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
console.log(xhr.responseText)
}
}

Is xhr always available inside onreadystatechange as a property of the argument? Which one to use?

My goal is to make a couple of XHR onreadystatechange handlers self-contained meaning that I'd like to get the XHR object inside them without using a closure (so I'm able to stack them in any order). Here's the main part of the code:
var xmlhttp;
function receiveOriginal()
{
if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
{
...
}
}
...
xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = receiveOriginal;
xmlhttp.open("GET", url, true);
xmlhttp.send();
Now, I'd like to make it like
function receiveOriginal()
{
xmlhttpInside = ... // get it any way without using a closure
if (xmlhttpInside.readyState == 4 && xmlhttpInside.status == 200)
{
...
}
}
...
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = receiveOriginal;
xmlhttp.open("GET", url, true);
xmlhttp.send();
I've found out that this actually works (at least in Vivaldi and Chrome):
function receiveOriginal(ev)
{
xmlhttpInside = ev.currentTarget;
// or
xmlhttpInside = ev.srcElement;
// or
xmlhttpInside = ev.target;
// each of these return true: xmlhttp === ev.currentTarget , xmlhttp === ev.srcElement , xmlhttp === ev.target
if (xmlhttpInside.readyState == 4 && xmlhttpInside.status == 200)
{
...
}
}
...
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = receiveOriginal;
xmlhttp.open("GET", url, true);
xmlhttp.send();
But the problem is, I'm looking throught a tutorial after tutorial and all of them, even the W3C reference (this is the final one, right?), all of them say nothing about the argument passed to a onreadystatechange handler. So I have no idea:
how widely is this supported?
which one (ev.currentTarget, ev.srcElement or ev.target) should I use, if any? What's the difference between them?
target, currentTarget, and srcElement are all properties of the native Event object (documentation), which onreadystatechange receives (because it is an event handler). There are many ways to handle this - all of which are perfectly fine. I personally prefer to use this for the onreadystatechange callback:
function receiveOriginal() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText)
}
}

Categories

Resources