views:

135

answers:

3

Hey folks

I stumbled on a piece of Ajax code that is not 100% safe since it's mixing asynchronous/synchronous type of code... so basically in the code below I have a jQuery.each in which it grabs information on the elements and launch an Ajax get request for each:

$(search).each(function() {
 $.ajax({
  url: 'save.x3?id='+$(this).attr("id")+'value='$(this).data("value");
  success: function(o){
   //Update UI
  },
  error: function(o){
   //Update UI
  }
 });
});

//code to do after saving...

So obviously the 'code to do after saving...' often gets executed before all the requests are completed. In the ideal world I would like to have the server-side code handle all of them at once and move //code to do after saving in the success callback but assuming this is not possible, I changed the code to something like this to make sure all requests came back before continuing which I'm still not in love with:

var recs = [];
$(search).each(function() {
 recs[recs.length] = 'save.x3?id='+$(this).attr("id")+'value='$(this).data("value");
});

var counter = 0;
function saveRecords(){
 $.ajax({
  url: recs[counter],
  success: function(o){
   //Update progress
   if (counter<recs.length){
    counter++;
    saveRecords();
   }else{
    doneSavingRecords();
   }
  },
  error: function(o){
   //Update progress
   doneSavingRecords(o.status);
  }
 });
}

function doneSavingRecords(text){
 //code to do after saving...
}

if (recs.length>0){
 saveRecords();  //will recursively callback itself until a failed request or until all records were saved
}else{
 doneSavingRecords();
}

So I'm looking for the 'best' way to add a bit of synchronous functionality to a series of asynchronous calls ?

Thanks!!

+1  A: 

If I understand what you're asking, I think you could use $.ajaxStop() for this purpose.

Drew Wills
Yes I was reading a bit on this but I don't think I get this 100%... I felt like this could possibly get in conflict with something else going on in the page. In the sense that ajaxStop is global to ANY XHR requests made on the page ? So a keep-alive module or a messaging system in another area on the page that makes frequent ajax calls this would impact ajaxStop as well, right ?
SBUJOLD
I _think_ that $.ajaxStop() waits only for ajax operations happening within the same jQuery instance, though I've never read any docs that were 100% clear on this point. So if you had non-jQuery ajax stuff happening or a 2nd instance of jQuery there wouldn't be a conflict.
Drew Wills
+1  A: 

Better Answer:

function saveRecords(callback, errorCallback){
  $('<div></div>').ajaxStop(function(){
    $(this).remove(); // Keep future AJAX events from effecting this
    callback();
  }).ajaxError(function(e, xhr, options, err){
    errorCallback(e, xhr, options, err);
  });

  $(search).each(function() {
    $.get('save.x3', { id: $(this).attr("id"), value: $(this).data("value") });
  });
}

Which would be used like this:

saveRecords(function(){
   // Complete will fire after all requests have completed with a success or error
}, function(e, xhr, options, err){
   // Error will fire for every error
});

Original Answer: This is good if they need to be in a certain order or you have other regular AJAX events on the page that would affect the use of ajaxStop, but this will be slower:

function saveRecords(callback){
  var recs = $(search).map(function(i, obj) {
   return { id: $(obj).attr("id"), value: $(obj).data("value") };
  });

  var save = function(){
   if(!recs.length) return callback();

   $.ajax({
    url: 'save.x3',
    data: recs.shift(), // shift removes/returns the first item in an array
    success: function(o){
     save();
    },
    error: function(o){
     //Update progress
     callback(o.status);
    }
   });
  }

  save();
}

Then you can call it like this:

saveRecords(function(error){
   // This function will run on error or after all 
   // commands have run
});
Doug Neiner
Yeah, I'd rather take a slight performance hit and go with the original answer you gave which I feel is more safe further down the line than ajaxStop.One thing though I think the .map call should be like this: var recs = $.map($(search), function(obj) { ... } );
SBUJOLD
@SBUJOLD `$(search).map` is correct, but I forgot to add the index to the functions. I updated my answer to fix it: `$(search).map(function(i,obj){ ... })` Sorry about that!
Doug Neiner
A: 

>> In the ideal world I would like to have the server-side code handle all of them at once and move //code to do after saving in the success callback

You'll need to think about this in terms of events. Closure's net.BulkLoader (or a similar approach) will do it for you:

See: goog.net.BulkLoader.prototype.handleSuccess_ (for individual calls) & goog.net.BulkLoader.prototype.finishLoad_ (for completion of all calls)

Geoff