-1

I am trying to send multiple AJAX request inside a for loop. I need to make sure that responses are getting linked to the right request that are being made. I am following the solution that is posted here XMLHttpRequest in for loop Here, fileBatches is the size of the loop. I am trying to capture the response which says the information about the number of requests succeeded and failed and trying to store it in _finalResponseArray. If the length of the fileBatches is 3, there are chances that first request may take more time than other request. Consider first request, takes sometime to process and by the time, second request and third request would have completed and then first request would complete. I need to make sure that right request is getting linked to right response. Here when the first loop starts(i=0), it takes sometime to get processed and by the time second loop starts (i=1) and gets processed. XHR[i].readyState == 4 (since i has incremented 1 and how to get the value of i = 0?) gets confused and I am not able to get the response linked to the right request. Please find the code below. Below function gets executed for multiple AJAX request.

var XHR = [];
var fileBatches = "Calling a function which returns array of values that needs to be processed"
var _finalResponseArray = [];
for (var i = 0; i < fileBatches.length; i++) 
{
   (function(i)
    {
      finalBatch = []
      finalBatch.push("Things that need to be processed by controller");

      finalData = finalBatch.join('&').replace(/%20/g, '+'); // Sending the values in a format so that it will be received by controller

      XHR[i] = new XMLHttpRequest();

      console.log(i);   

      XHR[i].open('POST', theURL);

      XHR[i].onreadystatechange = function (event) 
        {
          console.log("Here");
          if (XHR[i].readyState == 4) 
          {              
            console.log("This request is complete");
            console.log("I value is " + i);
          if (XHR[i].status == 200) 
          {
            _finalResponseArray.push(XHR[i].responseText);
            console.log("Inside" + _finalResponseArray);
          }         
        }
    }

    XHR[i].setRequestHeader('accept', 'text/JSON');

    XHR[i].send(finalData);
    })(i);
}

I am not sure what I am making wrong here, but the requests are not getting linked to correct responses and they randomly gets added to _finalResponseArray. It works perfectly if there is only one request without any loop. How to make sure that onreadystatechange is verified for correct loop?

**********Updates

Tried solution as suggested in comments and also various other approaches (referring past questions in stack overflow):

Even though I try to use closures, response messes up. For all 3 requests, it randomly picks a response and produces same response for all 3 requests.

Should my request have something unique so that response can track it correctly. I do see the iterator value 'i' was appended to URL when we try to send ant GET or POST request, but I am just sending same URL for different requests. Does that matter?

  • your indentation makes the code impossible to read – Jaromanda X May 22 '17 at 01:09
  • presumably `finalBatch` is delcared somewhere **not** in the code you've posted - and each loop will clobber it immediately - you probably don't need the `XHR` array either, because why would you need access to all the XHR's outside the loop? – Jaromanda X May 22 '17 at 01:10
  • as `XMLHttpRequest`'s are asynchronous, there's no guarantee in what order they will finish, so no guarantee what order the results will be pushed onto the result array - try using `_finalResponseArray[i] = XHR.responseText;` if you require the correct order - note that _finalResponseArray may have empty slots if some requests fail, and that, again, due to asynchronous nature of XHR, there's no way of knowing (with your current code) when all requests are completed – Jaromanda X May 22 '17 at 01:17
  • I updated my question. finalBatch is getting the right value and declared at the beginning of the function. So there is no need for XHR array? Based on the time all the requests are processed, the responses will be received if I define XHR as a variable rather than array? How to make sure that I receive all the responses by the time finalBatch loop is completed? – Sriram Chandramouli May 22 '17 at 01:32
  • `How to make sure that I receive all the responses by the time finalBatch loop is completed?` learn how to use asynchronous code ... use a callback, or promises, the choice is yours - but there's no `finalBatch` loop, there's ` `fileBatches` loop, and after that loop is completed, the only way to make sure that all responses are received is as I already said, learn how to work with asynchronicity – Jaromanda X May 22 '17 at 01:35

2 Answers2

3

Firstly, I'd recommend using so called XMLHttpRequest 2.0 - rather than the fiddly onreadystatechange stuff, just use onload and onloadend (you'll see why the latter in the code below)

Due to the asynchronous nature of XMLHttpRequest you can't predict when the requests are all done, so _finalResponseArray will only be complete once the final onloadend is complete

Adding a count of complete requests will allow you to do whatever you need in the onloadend callback once all requests have finished. onloadend is called regardless of success or failure

I also use .forEach instead of IIFE

var fileBatches = "Calling a function which returns array of values that needs to be processed"
var _done = 0;
var _count = fileBatches.length;
var _finalResponseArray = [];

fileBatches.forEach(function(item, i) {
    var finalBatch = []
    var xhr = new XMLHttpRequest();
    finalBatch.push("Things that need to be processed by controller");
    finalData = finalBatch.join('&').replace(/%20/g, '+'); // Sending the values in a format so that it will be received by controller

    xhr.open('POST', theURL);
    xhr.onload = function (event) {
        if (xhr.status == 200) {
            _finalResponseArray[i] = (xhr.responseText);
        }         
    };
    xhr.onloadend = function (event) {
        // this gets called in ALL cases, i.e. after load, error or abort
        if(++_done == _count) {
            // _finalResponseArray is complete here
        }
    };
    xhr.setRequestHeader('accept', 'text/JSON');
    xhr.send(finalData);
});
//_finalResponseArray will ALWAYS be empty here
Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
  • Thanks a lot for the suggestion. I am getting better understanding of asynchronous code, because of this project I am currently working on. Just to clarify, do you think xhr.onload and xhr.onloadend should be declared outside the forEach loop? Since it takes time to detect xhr.onload (as it depends on the processing time of controller), the loop continues and completes before the time onload and onloadend gets detected and it comes out of the loop before even onload and onloadend can run. Also, why we should pass in item and i to the function? I know i is used inside though. – Sriram Chandramouli May 22 '17 at 02:56
  • XHR[i].status == 200 should be xhr.status == 200, am I right? – Sriram Chandramouli May 22 '17 at 02:56
  • Thanks, I will try to include onload and onloadend outside the for loop and check the result and update here. Thanks again. – Sriram Chandramouli May 22 '17 at 03:00
  • outside the for loop? if you want, it's not the code I posted, it wont work, but you know better what you want – Jaromanda X May 22 '17 at 03:13
  • just posting the reason why I think, it is outside for loop here, I included the same reason in my above comment. ---- Just to clarify, do you think xhr.onload and xhr.onloadend should be declared outside the forEach loop? Since it takes time to detect xhr.onload (as it depends on the processing time of controller), the loop continues and completes before the time onload and onloadend gets detected and it comes out of the loop before even onload and onloadend can run. – Sriram Chandramouli May 22 '17 at 03:17
  • `do you think xhr.onload and xhr.onloadend should be declared outside the forEach loop?` - no, because that makes absolutely no sense, you want to track the progress of every request, and besides, `var xhr` is not visible outside the forEach loop – Jaromanda X May 22 '17 at 03:19
  • as far as `item` and `i` - `i` is the second argument, `item` is each item in the array, if you don't use it, then don't use it, but you can't change the position of arguments to suit your needs, javascript doesn't work like that – Jaromanda X May 22 '17 at 03:20
  • I think I misread few things here. I will check again and update here. Thanks a lot for you suggestions till now. Its been really helpful. – Sriram Chandramouli May 22 '17 at 03:27
  • I tried all sorts of thing, but I am struck at the same point, the response is not getting linked to right request. Consider 3 requests are sent, First request has more thing to process and it takes some time, by the time second request and third request gets completed, Even though I try to use closures, response messes up. For all 3 requests, it randomly picks a response and produces same response for all 3 requests. It would be great if you have some pointers to fix the issue – Sriram Chandramouli May 25 '17 at 21:06
  • I am not sure whether I should add something to the URL while I am sending request to make different request unique. – Sriram Chandramouli May 25 '17 at 21:08
1

You might want to pull the onreadystatechange function logic into its own function definition so that it can manage scope locally. Try this:

var XHR = [];
var _finalResponseArray = [];

var createReadyStateChangeCb = function(responseIdx)
{
    return function(event)
    {
        console.log("Here");
        if (XHR[responseIdx].readyState == 4) 
        {              
          console.log("This request is complete");
          console.log("I value is " + responseIdx);
          if (XHR[responseIdx].status == 200) 
          {
            _finalResponseArray.push(XHR[responseIdx].responseText);
            console.log("Inside" + _finalResponseArray);
          }
        }
    }
}

for (var i = 0; i < fileBatches.length; i++) 
{
   (function(i)
    {
      finalBatch = []
      finalBatch.push("Things that need to be processed by controller");

      finalData = finalBatch.join('&').replace(/%20/g, '+'); // Sending the values in a format so that it will be received by controller

      XHR[i] = new XMLHttpRequest();

      console.log(i);   

      XHR[i].open('POST', theURL);

      XHR[i].onreadystatechange = createReadyStateChangeCb(i);
    }

    XHR[i].setRequestHeader('accept', 'text/JSON');

    XHR[i].send(finalData);
    })(i);
}
pacifier21
  • 813
  • 1
  • 5
  • 13
  • By calling this, XHR[i].onreadystatechange = createReadyStateChangeCb(i); - say my first loop takes sometime (i=0) and onreadystatechange hasnt been detected, by the time, second loop (i=1) started and completed and now how to make sure onreadystatechange of first loop (XHR[0].onreadystatechange) can be called? – Sriram Chandramouli May 22 '17 at 03:13
  • To the person who down voted my question, could you please provide your reason for that so that corrections can be made for future posts? – Sriram Chandramouli May 25 '17 at 21:50