Search code examples
javascriptjquerydeferred

Deferred Async Tasks with jQuery


I'm new to deferred classes in JavaScript and would like to implement a function which will loop through forms and submit them one by one.

Looks like Deferred classes are the way to accomplish this.

I tried following this answer, but for some reason my implementation starts, waits 3 seconds and completes. I want it to show a different form name every 3 seconds until it's done with all the forms then complete.

What am I doing wrong? JSFIDDLE

function syncAll() {
        
  var promises = [];
  var forms = [
    {'name':'form 1'},
    {'name':'form 2'},
    {'name':'form 3'}, 
    {'name':'form 4'}];

  $.each(forms, function (index, value) {
    var def = new $.Deferred();
    setTimeout(function () {
      $("#output").html("Syncing: " + value.name);
      def.resolve({ 'message': 'finito!' });
    }, 3000);
    promises.push(def);

  });

  return $.when.apply(undefined, promises).promise();
}
    
    
    $.when(syncAll()).done(function(response){
        $("#output").html(response.message);
    });
    /*
    syncAll().done(function (response) {
      $("#output").html(response.message);
    }));
    */
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output">Start</div>


Solution

  • JSFiddle: https://jsfiddle.net/TrueBlueAussie/v6cgak1u/2/

    This uses a promise = promise.then(functionReturningNextPromise) pattern:

    function syncAll() {
        var promise = $.when();  // Start with a resolved promise.
        var forms = [
          {'name':'form 1'},
          {'name':'form 2'},
          {'name':'form 3'}, 
          {'name':'form 4'}];
    
        $.each(forms, function (index, value) {
            promise = promise.then(function(){
                var def = $.Deferred();
                setTimeout(function () {
                    $("#output").html("Syncing: " + value.name);
                    def.resolve({ 'message': 'finito!' });
                }, 3000);
                return def.promise();
            });
        });
        return promise;
    }
    
    
    $.when(syncAll()).done(function(response){
        $("#output").html(response.message);
    });
    

    This could be improved a bit by replacing the setTimeout and having a timed promise method instead that also chains together (still implemented using setTimeout).

    Note that new is not required on $.Deferred()

    EDIT :

    Features of javascript and jQuery can be exploited to make the code slightly more concise :

    function syncAll() {
        var forms = [
            {'name':'form 1'},
            {'name':'form 2'},
            {'name':'form 3'}, 
            {'name':'form 4'}
        ];
        return forms.reduce(function(promise, value) {
            return promise.then(function() {
                return $("#output").delay(1000).html("Syncing: " + value.name).promise();
            });
        }, $.when()).then(function() {
            return {'message': 'finito!'};
        });
    }
    
    syncAll().then(function(response) {
        $("#output").html(response.message);
    });
    

    This is essentially the same paradigm as above but exploits :

    • 'array.reduce()' to build the promise chain from the forms array.
    • jQuery's .delay() instead of setTimeout(), to implement delay.