4

I am iterating through an array of command files using the for each method.

With each command file, I need to wait for ajax success before continuing on with the next command.

The problem is that the for each loop moves on to the next command before the ajax code has finished. Can anyone offer a solution?

For each loop:

$.each(cmd_files, function(index, cmd) {

          update_log('Running CMD for' + cmd)

          wait_for_cmd_complete(cmd).done(function(data){

               update_log("CMD is complete");    

          })
 })

Ajax function:

function wait_for_cmd_complete(cmd){

     return $.ajax({
         type: 'POST',
         data: {cmd:cmd}, 
         url: 'wait_for_cmd_complete.php'
     });  


  }
Bacteria
  • 8,406
  • 10
  • 50
  • 67
user3101337
  • 364
  • 8
  • 24
  • use it if the ajax request sucessfuly completed...that is...in success part of the ajax function and insted of update_log("CMD is complete"); in loop echo it in tha ajax sucess,,,, – Akshay Sep 24 '15 at 17:34

4 Answers4

1

This is what I ended up getting to work.

  1. First Ajax request is complete.

  2. cmd file array is advanced using i++.

  3. Ajax function is called again from within the same function.

  4. If there are more files still to run, function is called again,

  5. else function exits after last cmd file is complete.

     number_of_cmd_files = cmd_files.length;
     update_log('Running CMDs')
     i=0;
     cmd= cmd_files[i];
    
     wait_for_cmd_complete(cmd)
    
     function wait_for_cmd_complete(cmd){
    
        update_log("Waiting for CMD " + cmd + " to complete...")
    
       $.ajax({
            type: 'POST',
            data: {cmd:cmd}, 
            url: 'wait_for_cmd_complete.php'
    
        }).done(
             function(value) {
             i++;  
             update_log( "    CMD complete for: " + cmd);
             if (i < number_of_cmd_files) {
               cmd = cmd_files[i]
               wait_for_cmd_complete(cmd);  
             }
           }
        ); 
    

    }

user3101337
  • 364
  • 8
  • 24
1

That's simply not how you write event driven actions. If you need the next iteration of code to only start after an event then you don't loop through the iterations...since that would run all the code before the event! That's just how events work.

Making something like this general structure would work better for running 1 iteration of code every event:

var i = 0; // the index we're using
var list = []; // array of the things you plan on "looping" through
var max = list.length; // basically how many iterations to do

function nextIteration() {
    if (i >= max) return; // end it if it's done
    // do whatever you want done before the event for this iteration
    list[i].addEventListener("someevent", onEvent); // add whatever event listener
}

function onEvent() {
    // do whatever it is you want done after the event for this iteration
    i++; // up the index
    nextIteration(); // start the next iteration
}

nextIteration(); // start the first iteration manually

For illustrative purposes so that you can know what's going on, here's your code formatted like my above code.

var i = 0; // the index we're using
update_log('Running CMDs');
var cmd; // basically a var just so we don't have to keep calling cmd_files[i]
var totalCommands = cmd_files.length; // basically how many iterations to do

function sendNextCommand() {
    if (i >= totalCommands) return; // end it if it's done
    cmd = cmd_files[i]; // again, just so we don't have to keep calling cmd_files[i]
    update_log("Waiting for CMD " + cmd + " to complete...");
    $.ajax({type:'POST', data:{cmd:cmd}, url:'wait_for_cmd_complete.php'}).done(onCommandComplete);
    // above line does what needs to be done (sends to PHP) and then adds the event listener 'done'
}

function onCommandComplete(value) {
    update_log( "    CMD complete for: " + cmd);
    i++; // up the index
    sendNextCommand(); // start the next iteration
}

sendNextCommand(); // start the first iteration manually
Jimbo Jonny
  • 3,549
  • 1
  • 19
  • 23
  • The only difference I see here is that you are using another function to call the main function. My code does the same thing without the extra function. The next piece of code won't start until the response comes back from the ajax request. I'm a relative noob at programming, so I could well be wrong. – user3101337 Sep 28 '15 at 23:30
  • @user3101337 - you make a function for it too, you're just using an anonymous one rather than giving it a name (it's inside your calling of `done()`) and you're remaking an instance of that anonymous function every time wait_for_cmd_complete is called rather than making it once and just calling it repeatedly. – Jimbo Jonny Sep 29 '15 at 05:16
  • Aside from that, the main difference between my code and yours is that mine was laid out with the intention of being generic and easily read/understood exactly what was happening. Your code answers the question, in that it is code that solves the problem. Mine is the same general concept but made to answer the question in a way that would more easily teach what's going on and why so others can apply it to their own issue. It's also not jQuery dependent code, it's just JS. Same could be done with jQuery events in the same structure as I write it though as well. – Jimbo Jonny Sep 29 '15 at 05:18
  • Thank you for the detailed explanation, I really don't know much about this stuff, as you can no doubt tell. I didn't know making another instance of a function is worse then just calling an existing function, although it make sense now that I think about it. With your code, I wasn't sure how to use the event listener portion, or at least how to use it in my case. Not sure how that would work instead of the ajax.done. – user3101337 Sep 29 '15 at 18:24
  • @user3101337 - the plain JS version of events really isn't much different than the way you're doing it in jQuery. Your anonymous function is sorta like this: `someObject.someEvent(function(){});` and the non-anonymous way would be `function myFunction(){} someObject.someEvent(myFunction);`. It can be reversed too; my plain JS looked like this `function myFunction(){} someObject.addEventListener('some_event', myFunction);` but could be done with an anonymous function like `someObject.addEventListener('some_event', function(){});` Both can be done both ways, it's not a plain JS vs jQuery thing. – Jimbo Jonny Sep 29 '15 at 19:02
  • I'd also add that this is general event handling knowledge, but the code is far from universal in plain JS. Javascript is actually not that well structured of a language...it has a really diverse history that has led to a lot of weirdness, especially with events and loading XML and stuff like that. jQuery helps fix some of that and make a more universal way of doing stuff, handling all the weirdness internally. My point isn't to stop using jQuery, it's just to illustrate the general programming concepts involved all around. – Jimbo Jonny Sep 29 '15 at 19:11
  • Edited to add your code changed to my general structure. That should help you get the jist of what's going on. – Jimbo Jonny Sep 29 '15 at 19:39
  • How do you get the data from the ajax response into the onCommandComplete function for processing? – user3101337 Oct 01 '15 at 00:43
  • In either case (your code or mine) the data from the response is passed to whatever function you define for "done". In your code that's the 'value' argument you put in your anonymous function. In my code I simply didn't include the argument because you weren't actually using the response data. But if you wanted to you'd do the same thing, give that function an argument to accept the response data. I'll modify that last part of code to show it. – Jimbo Jonny Oct 01 '15 at 00:57
  • Ah, I was trying .done(onCommandComplete(value)) and not getting anywhere. I didn't realize the response was automatically sent to that function. Last question: How does the onCommandComplete() function know the values of i and cmd if they are not passed into it? – user3101337 Oct 01 '15 at 01:17
  • Variables declared outside of a function are still accessible in it. Look at this example: http://jsfiddle.net/361xbdr8/ It's the opposite that isn't true, a variable declared inside a function isn't accessible outside. – Jimbo Jonny Oct 01 '15 at 01:52
  • Thank you, Jimbo. I thought it was bad practice to use global variables, but I'm going to do it the way you have done it. Thank you so much for the programming lessons. I have marked your response as the accepted answer. – user3101337 Oct 01 '15 at 17:49
  • Global variables are more of an issue when you're writing a reusable code package or something, not as much of an issue for the specific page code you write on a single given page. And even for code packages the way to fix it is to learn actual class based javascript programming, not really to just try to make every variable local to a function. It doesn't hurt to use a little more specific variable names than 'cmd' to avoid conflicts, but you don't have to fear having a few global variables on your page. – Jimbo Jonny Oct 01 '15 at 19:12
0

Maybe try chaining your events. Not overly familiar with this approach, but I think this will work:

$.each(cmd_files, function(index, cmd) {
            update_log('Running CMD for' + cmd);
            var request =  $.ajax({type: 'POST',data: {cmd:cmd}, url: 'wait_for_cmd_complete.php'});
            request.then( update_log("CMD is complete");        
 });
Mark
  • 4,773
  • 8
  • 53
  • 91
  • Thanks, Mark. Tried this, but the request.then happens even if the ajax hasn't finished. Same problem I'm having now. – user3101337 Sep 24 '15 at 20:07
0

Using synchronous ajax should work, at least if you don't do it cross domain. Add async: false to the ajax options. See here and here for some more information.

Patrick Ziegler
  • 787
  • 1
  • 7
  • 20
  • 1
    async option has been deprecated in the latest version of jquery. It can cause a bad experience for the end user and is best not to use. – user3101337 Sep 25 '15 at 17:33
  • Yep you're right, didn't know that. All my jQuery knowledge is basically pre 1.8. So if you can't do that, the solution you came up with is probably the best – Patrick Ziegler Sep 25 '15 at 19:18