0

I've looked everywhere, and I am not quite getting if it's possible or how to loop through an ajax request, cycling through the values in an array.

So it would need to make an ajax request one of the data values (serial) as array[0], finishing the request, then does the next request with array[1], and so on.

My code:

$.ajax({
    url: 'example.com',
    type: 'GET',
    data: {
        message:  message,
        user: user,
        serial: i
    },
    success: function(response) {
        alert("Sent");
    },
     error: function(XMLHttpRequest, textStatus, errorThrown) {
           alert("Fail");
        }       
});

So this will work for one defined serial, but how would it work when serial (the variable 'i') is an array, holding many serials? Also it shouldn't send the array, it needs to cycle through, sending one value at a time.

Any help is most appreciated at this point.

Michael
  • 239
  • 3
  • 12
  • 2
    Why would you want to make multiple requests for each serial? What is wrong with returning all of the ones needed in one request? – Ryan Wilson Mar 07 '18 at 13:37
  • @RyanWilson Thanks for your comment, it's just the way the code is set up on the page it is posting data to. (redirects) – Michael Mar 07 '18 at 13:40
  • @NicholasKyriakides thanks for your comment also! I did see that post already, and it was not working for me. – Michael Mar 07 '18 at 13:42
  • 1
    Well, what was not working? Perhaps you can post your code so we can have a look? – nicholaswmin Mar 07 '18 at 13:43
  • @Michael This sounds like bad design and re-coding it to just make one call to the server and then add logic to use all values returned would be more efficient, but if you really need to do it in a loop, I would recommend the link that was provided by Nicholas Kyriakides – Ryan Wilson Mar 07 '18 at 13:43
  • use foreach, and ajax with async:false, – Ahmed Sunny Mar 07 '18 at 13:44
  • 3
    @AhmedSunny That is poor design as well, ajax is meant to be asynch. – Ryan Wilson Mar 07 '18 at 13:45
  • Is there a relation between the AJAX calls? Should they be synced ? – Hamza Abdaoui Mar 07 '18 at 13:45
  • 1
    @AhmedSunny Please do not **ever** do that. Apart from the fact that it's deprecated, it's also terrible UX. – nicholaswmin Mar 07 '18 at 13:46
  • @RyanWilson i know, but for this question he asked, this is i think he needed. – Ahmed Sunny Mar 07 '18 at 13:48
  • I agree with Nicholas. There are ways to design your code to emulate synchronous looping. – Ryan Wilson Mar 07 '18 at 13:50
  • @RyanWilson it's possible the service he's calling isn't owned by him – kvsm Mar 07 '18 at 13:52
  • Thanks for all your help, really appreciated, and I'm sure it will help others too. For each works nicely. I think I know why, but do you mind very briefly explaining why its bad practice, just as it's not efficient to make so many requests? – Michael Mar 07 '18 at 13:52
  • @Michael We left comments under that answer – nicholaswmin Mar 07 '18 at 13:52
  • @NicholasKyriakides sorry just saw that, many thanks – Michael Mar 07 '18 at 13:53
  • @RyanWilson Thanks very much, do you know ways to emulate sync looping? – Michael Mar 07 '18 at 13:56
  • 1
    @Michael Look into Callbacks and/or promises. Here is another SO post which has some good answers which show callbacks and recursion, (https://stackoverflow.com/questions/35881872/how-do-i-make-ajax-synchronous-sjax) – Ryan Wilson Mar 07 '18 at 14:00
  • @RyanWilson Really appreciate that, thanks – Michael Mar 07 '18 at 14:05
  • You want to minimize the amount of round trips because you put unneeded strains on the network. Doing a single fetch is faster and better than doing multiple requests. If you can avoid doing loops, it's best to gather all the info you need on the client and get it out of the server. Or make your ajax requests event based.. Which is fine. – sksallaj Mar 07 '18 at 14:21
  • I love how people are entirely overcomplicating this with promises and whatnot. Just keep it simple; call the function in the success/complete callback. – NoobishPro Mar 07 '18 at 14:51

3 Answers3

1

Create a recursive function that does an ajax call. When the ajax call ends, the function calls itself (recursion) and passes in an updated index value to use on passed in array for the next ajax call.

 /**
 * Recursive function that make an ajax call over the {index} element inside an {array}
 * @param {Array} array the array to loop through 
 * @param {Number} index current index 
 */
    function Caller(array, index){
       if(array.length <= index){return;}
       $.ajax({
        url: 'example.com',
        type: 'GET',
        data: {
            message:  message,
            user: user,
            serial: array[index]
        },
        success: function(response) {
            alert("Sent");
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {
            alert("Fail");
        },
        complete: function(){
            Caller(array,++index);
        }
    }

The recursive function calls itself on the complete callback (which is triggered after the call completes whether is was a success or error).

By doing it this way you get through the array and only send an ajax request when the previous request is finished.

Andrew Lohr
  • 5,380
  • 1
  • 26
  • 38
LPZadkiel
  • 561
  • 1
  • 3
  • 16
  • 1
    Wouldn't you need array.length <= index, as .length would return 1 more than what is the last index of the array, also, in your complete I think you would need ++index – Ryan Wilson Mar 07 '18 at 14:05
  • 4
    it would be nice to give an explanation on why/how your answer works so OP can learn and understand as well – Andrew Lohr Mar 07 '18 at 14:06
  • about the if condition you are right. and about the explanation i think the code explained by itself – LPZadkiel Mar 07 '18 at 14:15
  • @LPZadkiel Thanks so much for your answer, so array would be 'i'? I've tried running it and it doesn't seem to run, not sure why, will keep trying.. – Michael Mar 07 '18 at 14:22
  • @LPZadkiel the code absolutely does not explain itself to a person who it new to this. – Andrew Lohr Mar 07 '18 at 14:27
  • @Michael the array parameter would be your array so you can start this recursion off by doing `var myArray = [1,2,3,4]; Caller(myArray, 0);` outside of the Caller function – Andrew Lohr Mar 07 '18 at 14:29
  • @AndrewLohr Thanks v much Andrew, will test it out – Michael Mar 07 '18 at 14:32
  • 1
    i edited my post explaining the code, my english is not that good, i did my best – LPZadkiel Mar 07 '18 at 15:23
-1

Try to use forEach():

array.forEach(element => {
    $.ajax({
        ...
        data {.., serial: element...}
        ...
    });
});
  • OP says he needs to finish the request before doing the next request in the array. Won't this just send off every request simultaneously? – Andrew Lohr Mar 07 '18 at 13:50
  • 2
    @AndrewLohr Yes it would, while also possibly triggering `429 - Too many requests` errors on any sensibly-designed API. – nicholaswmin Mar 07 '18 at 13:51
  • @NicholasKyriakides Many thanks again, I understand. Do you know of ways to emulate synchronous looping. – Michael Mar 07 '18 at 13:55
-2

It's 2018 so there are multiple, nice ways of doing this;

  • You can use Promises, $.ajax actually returns one; and async/await to perform XHR requests serially.
  • You can keep your callback-style code and use a small utility function to abstract the async iteration in a nice readable way that you can reuse over and over.

I'll cover both cases.

Async Iteration with Promises and async/await

Since jQuery 1.5, $.ajax returns a Promise. So if you're using a modern browser you can just await it.

This is by far most elegant and terse way since the code looks like synchronous code hence it's far more readable. Be aware that while the code looks synchronous, it's in fact non-blocking.

const getPosts = async (pages) => {
  const posts = []

  for (const page of pages) {
    const post = await $.ajax({
      url: 'https://jsonplaceholder.typicode.com/posts/' + page
    })

    posts.push(post)
  }

  return posts
} 

getPosts([1, 2, 3, 4, 5]).then(posts => {
  console.log(posts)
}).catch(err => {
  console.error(err)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Async Iteration with callbacks

This is the "traditional" way of doing asynchronous operations, that uses callbacks. This style doesn't require a modern browser at all since at it's base level it just passes around functions to achieve non-blocking behaviour.

However, this type of code is far harder to work with; You need to rely on utility functions that wrap common operations (looping, mapping etc) to effectively work with such code.

I've written a small utility function that let's you:

  • Iterate over the elements of an Array.
  • Specify a callback function that get's called for each iteration
  • This function get's called on each iteration. The currently-iterated over Array element is passed to it, together with a next argument that you need to call in order to proceed to the next iteration.
    • Calling the next of this callback function pushes the result into a final result Array, and proceeds to the next iteration.
  • Specify a final callback function that get's called when all the iterations have finished.

If I'm not mistaken, this is identical in operation to the async.mapSeries method of the popular async module.

async.mapSeries:

In the following example, I'm passing an Array of posts to fetch from a REST API.

When all the iterations are complete, the posts argument in the final callback contains an Array with 5 posts.

It takes advantage of error-first callbacks, a common pattern to gracefully propagate errors up the callback chain if something goes awry in your async operations.

// async.mapSeries utility Function

const async = {
  mapSeries: function(arr, onIteration, onDone, { i = 0, acc = [] } = {}) {
    arr.length ?

    onIteration(arr[i], (err, result) => {
      if (err) return onDone(err)

      acc.push(result)

      acc.length < arr.length ?
        this.mapSeries(arr, onIteration, onDone, {
          i: ++i, acc
        }) : onDone(null, acc)
    })

    : onDone(null, arr)
  }
}

// Usage

async.mapSeries([1, 2, 3, 4, 5], (page, next) => {
  $.ajax({
    url: 'https://jsonplaceholder.typicode.com/posts/' + page,
    success: response => {
      next(null, response)
    },
    error: (XMLHttpRequest, textStatus, err) => {
      next(err)
    }
  })
}, (err, posts) => {
  if (err) return console.error('Error:', err)

  console.log(posts)
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
nicholaswmin
  • 21,686
  • 15
  • 91
  • 167