1

I currently have some jQuery code that looks a bit like this:

for ( i = 0; i < limitVar; i++ ) {
doAjaxStuff(i);
}

function doAjaxStuff( i ) {
Here we make a SYNCHRONOUS ajax call, sending i.
}

The ajax call needs to be synchronous - one isn't fired until the last one is done.

As synchronous JS is deprecated, I want to move this code to use promises. How would I achieve this? I've been unable to find an example that close enough fits this situation.

vancoder
  • 178
  • 9
  • @KevinB Promise.all() is firing all requests at once. If array is large enough that's quite surely going to exhaust resources resulting in failing requests. – Thomas Urban Aug 14 '17 at 21:31
  • You can use [].reduce to create a chain that calls them one after the other. Here for example: https://stackoverflow.com/questions/21372320/how-to-chain-execution-of-array-of-functions-when-every-function-returns-deferre – Kevin B Aug 14 '17 at 21:31
  • 1
    Moving to promises means moving to asynchronous. – Bergi Aug 14 '17 at 22:55

3 Answers3

3

You don't do synchronous ajax in the browser (well technically, you can in some circumstances, but it's a really bad idea to do so because it locks up the browser during the ajax call).

Instead, you redesign your loop so that it only carries out the next ajax call when the previous one is done which means you have to loop manually, you can't use a for loop. Since your code is pseudo-code (you don't show the real ajax operation), I'll use a jQuery ajax example, but you can substitute any ajax function you have as long as it either returns a promise or uses a callback to signal when its done.

The general idea is that you create a function for your ajax call and you use the completion callback from that to increment your index and then run the next iteration of your loop.

function runLoop(data) {
    var i = 0;

    function next() {
        if (i < data.length) {
            return $.ajax(data[i]).then(function(data) {
                ++i;
                return next();
            });
         else {
             // all done with loop
         }
    }
    return next();
}

// call it like this
runLoop(someArray).then(function() {
   // all done here
});

If you don't have an array of data, but just want a loop index:

function runLoop(limitVar) {
    var i = 0;

    function next() {
        if (i < limitVar) {
            return $.ajax(something_with_i_in_it).then(function(data) {
                ++i;
                return next();
            });
         else {
             // all done with loop
         }
    }
    return next();
}

// call it like this
runLoop(theLimit).then(function() {
   // all done here
});

If your limitVar is not large and there is no other logic involved in deciding whether to continue the loop, you can also use a little bit simpler pattern if you have an ajax function that returns a promise:

function runLoop(limitVar) {
    var p = Promise.resolve();
    for (var i = 0; i < limitVar; i++) {
        p = p.then(function(prevResult) {
            return someAjax(i);
        });
    }
    return p;
}

// call it like this
runLoop(theLimit).then(function() {
   // all done here
});

If you aren't using ajax functions that return a promise, then it's only a few lines of code to wrap your function with one that does and then you can more easily use these design patterns.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    Why the downvote? What is wrong with this answer? People can only improve their answers to address a downvote objection if you provide feedback for why it's downvoted. – jfriend00 Aug 14 '17 at 21:31
0

Process the array using some separate function. Each time you take off another element from array, then process it and when it's done call the function again. If there is no more item in list then the whole process is done.

var listOfRequests = ...;

new Promise( function( resolve, reject ) {
   requestNext();

   function requestNext() {
       if ( !listOfRequests.length ) {
           return resolve();
       }

       var next = listOfRequests.shift();

       doAjaxStuff( next, reject, requestNext );
   }
} )

doAjaxStuff( request, errCallback, doneCallback ) {
    ...
}
Thomas Urban
  • 4,649
  • 26
  • 32
-1

this is a pretty simple pattern:

var queue = Promise.resolve();
var nop = () => null;

for(let i=0; i<limitVar; ++i){
  queue = queue.then(() => doAjaxStuff(i));
  //or if you want to ignore Errors
  //queue = queue.then(() => doAjaxStuff(i)).catch(nop);
}

queue.then(() => console.log("finished"));

Or if you use an Array as input:

var done = data.reduce(
    (queue, value, index) => queue.then(() => doSomethingWith(value, index)), 
    Promise.resolve()
  );

done.then(() => console.log("finished"));
Thomas
  • 11,958
  • 1
  • 14
  • 23