-3

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?

user3425506
  • 1,285
  • 1
  • 16
  • 26
  • 1
    It's because "session blocking", not because the ajax. If you tried it without session it would work in parallel. https://stackoverflow.com/a/15693029/3807365 – IT goldman Jul 27 '22 at 13:05
  • 1
    It has nothing to do with that, and everything to do with the fact tht you wait for `xhr.onreadystatechange` to show the first call has completed before even initiating the second – Jamiec Jul 27 '22 at 13:06
  • 1
    Why would you expect the XHR call for `ajax-b` to happen before `ajax-a` completes? You don't call `callback` until `ajax-a` is done. So naturally, the two calls happen one after another. (If you fixed that and actually made the calls one right after another, without waiting, you might run into session blocking, but right now the second isn't even starting until the first is done.) – T.J. Crowder Jul 27 '22 at 13:07
  • I agree with @T.J. Crowder that my expectation does not seem logical. However, I do not know enough about the execution of the code to know for sure. My question remains which is how can I, or can I, use callbacks to achieve this asynchronously ie. in 5 seconds not 10? – user3425506 Jul 27 '22 at 13:13
  • @IT goldman I have not created a PHP session here with session_start(). Hopefully that shouldn't be a problem? – user3425506 Jul 27 '22 at 13:22
  • 2
    Yes it already been established that it's not session issue. But could have been – IT goldman Jul 27 '22 at 13:24

2 Answers2

2

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();
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • Don't forget to check for HTTP failure before calling `text`. :-) – T.J. Crowder Jul 27 '22 at 13:17
  • @Quentin I really appreciate your response but will need to take more time to try and understand it. I want to get my head round the callback approach before moving on to the newer methods. – user3425506 Jul 27 '22 at 13:26
  • @T.J. Crowder not sure if your comment re HTTP failure was for me or Quentin or both but unfortunately I have no idea what you mean. – user3425506 Jul 27 '22 at 13:27
  • 2
    @user3425506 Good thing T.J. Crowder [has a blog in which he explains it in detail](http://blog.niftysnippets.org/2018/06/common-fetch-errors.html). ;-) – Ivar Jul 27 '22 at 13:29
  • 2
    @user3425506 - It was for Quentin. I'm talking about the fact `fetch` only rejects the promise it returns on *network* error, not HTTP error (like a 404 or 500). You have to check the `ok` or `status` property of the returned Response object to know if the HTTP call worked. Here's a writeup on my very old anemic blog: http://blog.niftysnippets.org/2018/06/common-fetch-errors.html Edit: LOL, Ivar linked it. :D – T.J. Crowder Jul 27 '22 at 13:29
  • I have unaccepted this answer because it has needless complication in it. I used the main feature of this answer to provide a much better answer myself. If you disagree please explain why. – user3425506 Jul 27 '22 at 16:31
  • It was complication you introduced in your original code! – Quentin Jul 27 '22 at 21:51
0

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>
user3425506
  • 1,285
  • 1
  • 16
  • 26