How to make callback run asynchronously? - javascript

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>

Related

Unable to get the same results with 2 pieces of somewhat similar XMLHttpRequest

Hope you are able to help or just help me understand why I have 2 almost similar codeblocks where one does not do what I expect.
I am trying to make some API calls where I populate a variable with the data that is pulled from the API call. In the first there is no problem at all, but the second I can't populate the variable.
I have tried googling the problem and it seems to be because of the asynchronous nature of XmlHttprequests. But again, I do not get why one solutions works and another don't.
The solution that work:
// Get JSON and convert it to an object
var obj;
const Http = new XMLHttpRequest();
const url = "https://type.fit/api/quotes";
Http.open("GET", url);
Http.send();
Http.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
obj = JSON.parse(Http.responseText);
}
};
In this solution I am able to get the data and populate the variable obj and use it globally.
link to the solution: https://codepen.io/Kfriis/pen/QWjGZmx
The solution that don't work:
//function that takes a currency in capital letters and returns
var rates;
var currency = 'gbp';
const currencyString = currency.toUpperCase();
const API = "api.frankfurter.app"
const URL = `https://${API}/latest?amount=1&from=${currencyString}&to=DKK`
const http = new XMLHttpRequest();
http.open("GET", URL);
http.send();
http.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
rates = JSON.parse(http.responseText);
}
};
console.log(rates)
This do, for some reason, not work. I do not get why the rates variable do not get populated since the request is basically the same for the first code snippet.
I have come down to an idea of it being because the data sent from the 2 API endpoints may be different in some way. Because if it was only because of the asynchronous requests, both code snippets should return undefined.
Link https://codepen.io/Kfriis/pen/VwvpKmd
I do hope someone is able to shine some light on this.
I must be doing something wrong in the second snippet, because I can console.log() from inside the onreadystatechange but not outside it. Which led me to believe for a long time that it was a scoping issue.
Your code does work. However, you're logging console.log(rates) outside the http.onreadystatechange-function which means you're logging the rates before you get the response. If you change the code block
http.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
rates = JSON.parse(http.responseText);
}
};
console.log(rates)
to
http.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
rates = JSON.parse(http.responseText);
console.log(rates)
}
};
it should work.
Here's a working example code if you wanna add the code to a function.
// Function that takes a currency in capital letters and returns
function getCurrencyRates(currency, cb) {
const currencyString = currency.toUpperCase();
const API = "api.frankfurter.app"
const URL = `https://${API}/latest?amount=1&from=${currencyString}&to=DKK`
const http = new XMLHttpRequest();
http.open("GET", URL);
http.send();
http.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
cb(JSON.parse(http.responseText));
}
};
}
// Call the function and pass "gbp" as currency.
// Rates will be logged in response.
getCurrencyRates('gbp', function(response) {
console.log(response);
});

Can't access page using xmlhttprequest

I have an xmlhttprequest code that is executed on a button, it runs and access the advReqPage.aspx on the first run but when I press the button again, it doesn't access the advReqPage.aspx any more. What is the problem here?
function SaveAdvPayment() {
var xhr = new XMLHttpRequest();
var ornumber = document.getElementById("ORNumber").value;
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
if (xhr.status === 200) {
// OK
alert('response:' + xhr.responseText);
// here you can use the result (cli.responseText)
} else {
// not OK
alert('failure!');
}
}
}
xhr.open("GET", "Server_Requests/advReqPage.aspx?poo=" + ornumber + "&sess=INSERT", false);
xhr.send();
alert('Saved');
$('#myModal').modal('hide');
}
Probably the first response is getting cached and when you make the second request your browser is not making this new request. This behavior is due to browser locking the cache and waiting to see the result of one request before requesting the same resource again. You can overcome this by making your requests unique like adding random query string.

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

AJAX code order's difference

I saw most of the AJAX examples in W3school look like this:
function loadDoc() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
document.getElementById("demo").innerHTML = xhttp.responseText; //get response
}
};
xhttp.open("GET", "ajax_info.txt", true);
xhttp.send(); //send request
}
I changed the order of the code like this:
function loadDoc() {
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "ajax_info.txt", true);
xhttp.send(); //send request
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
document.getElementById("demo").innerHTML = xhttp.responseText; //get response
}
};
}
and it seems like it works too, and my brain will think second one more logic.
So my question is: Is there any rules on this AJAX code's order? Will it make any differences for the 2 example above in any situation?
The AJAX request is handled asynchronously so the order of definition of the callback does not matter.
I have annotated your code with some notes. The execution order (in this case) will be:
1 -> 2 -> 3 (Request Triggered) -> 4 (Define callback)
|
5 (Callback function called asynchronously every time the request state changes)
|
6 Execute logic if request is successful
Steps 1 (Creation of XHR Object), 2 (Opening request) and 3 (sending request) must be in sequence. Step 4 can happen any time after the creation of the object as long as it's before the network response or state change happens.
Reading up on AJAX and Asynchronous Callbacks in JS will help understand this better. You can start here.
function loadDoc() {
//1. Create XHR Object
var xhttp = new XMLHttpRequest();
//2. Define request parameters
xhttp.open("GET", "ajax_info.txt", true);
//3. Trigger Request
xhttp.send();
//4. Define callback for state change
xhttp.onreadystatechange = function() {
//5. This code is executed every time the state of the request changes
if (xhttp.readyState == 4 && xhttp.status == 200) {
//6. This code executes only if readyState is 4 (request is done) and the http status is 200 (success)
document.getElementById("demo").innerHTML = xhttp.responseText; //get response
}
};
}

Passing a return value

Please help, I have been looking at this all day and I know there must be a simple fix!
How do I pass results back to textService so that I can make a call such as
textResult = textService(text to pass in);
I don't want to use a global variable if I can avoid it.
This is the code
function textService(text){
req.open("GET", "http://....?text="+text, true);
req.onload = showResults;
req.send(null);
}
function showResults() {
results = req.responseXML.getElementsByTagName("Result");
}
Thank you in advance
function textService(text){
// set flag to false for sync requests
req.open("GET", "http://...?text="+text, false);
req.send(null);
// browser will be stalled till request is complete.
if(req.status == 200 && req.readyState == 4) {
return req.responseXML.getElementsByTagName("Result");
} else {
return 'request failed';
}
}
// javascript will stall till request is complete.
var results = textService('someText');
Note, making synchronous request can be harmful, if a request fails, it might stall the browser indefinatly. It's better to do it asynchronously.
function textService(text, callback){
// async is true by default, no need to pass 3rd param.
req.open("GET", "http://...?text="+text);
req.send(null);
req.onreadystatechange = function(){
if(this.readyState == 4 || this.status == 200) {
callback(this.responseXML);
}
}
}
textService('someText', function(xml){
// do stuff with XML.
});
Just need switch your coding mind to async programming ;)
You can use this
function showResults() {
results = this.responseXML.getElementsByTagName("Result");
}

Categories

Resources