nice ass
nice ass

Reputation: 16719

Resolve deferred object after other multiple deferred objects are resolved

My experience with $.Deferred is very limited, and so far the code the came looks very messy.

The function that is supposed to return a promise is updating the DOM with HTML received after an ajax request.

It's used like this:

this._refreshWorkspace(response.html).then(function(){
  // do things after DOM update finished
});

Here's the function code:

_refreshWorkspace: function(htmlBlocks){

  var dfd = $.Deferred();

  if('editor' in htmlBlocks){
    app.destroy(this.editor).then((function(){
      this.editor.empty().append(htmlBlocks.editor);
    }).bind(this)).then((function(){
      app.refresh(this.editor);
    }).bind(this));
  }

  if('listPanels' in htmlBlocks){
    app.destroy(this.list).then((function(){
      this.list.empty().append(htmlBlocks.listPanels);
    }).bind(this)).then((function(){
      app.refresh(this.list);
      // other unrelated code here
      dfd.resolve();

    }).bind(this));
  }

  if('listNav' in htmlBlocks){
    // similar code block       
  }

  return dfd;
},

It seems to work but only if the "listPanels" htmlBlock is provided. I want the dfd to be resolved once after all refresh calls, or even better if possible - after all refresh calls are resolved. Any ideas on how could I make this happen?

Upvotes: 1

Views: 714

Answers (2)

Roamer-1888
Roamer-1888

Reputation: 19288

As already explained, the simple answer to the question is to aggregate the individual promises with $.when.apply(null, promiseArray).

However, if all the code in the question is representative of the treatment to be applied to all html blocks, then you can go further.

jQuery.map() will operate on ownProperties of an object so can be exploited to iterate over htmlBlocks, resulting in a concise, generalised main routine with a couple of supporting hashes.

_refreshWorkspace: function(htmlBlocks) {
    var that = this; // avoids the need for .bind(this) in the promise chain and the methodsHash
    var propHash = {
        'editor': 'editor',
        'listPanels': 'list'
    };
    // All the "other unrelated code" is defined here
    var methodsHash = {
        'editor': null,
        'listPanels': function(key, obj) {
            ...
        },
        ...
    };
    //And the main routine is a concise $.map(htmlBlocks, ...) structure. 
    var promises = $.map(htmlBlocks, function(html, key) {
        var obj = that[propHash[key]];
        return app.destroy(obj).then(function() {
            obj.empty().append(html); //if empty() and append() are jQuery methods then this line is synchronous.
            return app.refresh(obj);// if app.destroy() is asynch and theanable, then it seems safe to assume that app.refresh() is also asynch and theanable. Therefore return the result here.
        }).then(function() {
            if(methodsHash[key]) {
                methodsHash[key](key, obj);
            }
        });
    });

    //Now aggregate `promises` into a single promise which resolves when all the promises resolve, or rejects when any of the promises rejects.
    return $.when.apply(null, promises);
},

Now, to cater for all other html blocks, just add one line to propHash and a null or function to methodsHash. Providing the main routine is comprehensive, it won't need amending.

IMHO, this a better way to organise the code.

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074355

Put all of the promises from the loops into an array, then use $.when. Sadly, using $.when with an array is ugly:

return $.when.apply($, theArray);

...because $.when is designed to accept discrete arguments rather than an array.

Something like this:

_refreshWorkspace: function(htmlBlocks){

  var promises = [];

  if('editor' in htmlBlocks){
    promises.push(
      app.destroy(this.editor).then((function(){
        this.editor.empty().append(htmlBlocks.editor);
      }).bind(this)).then((function(){
        app.refresh(this.editor);
      }).bind(this))
    );
  }

  if('listPanels' in htmlBlocks){
    promises.push(
      app.destroy(this.list).then((function(){
        this.list.empty().append(htmlBlocks.listPanels);
      }).bind(this)).then((function(){
        app.refresh(this.list);
      }).bind(this))
    );
  }

  if('listNav' in htmlBlocks){
    // similar code block       
  }

  return $.when.apply($, promises);
},

Here's a live example using random Deferreds:

function doSomething() {
  var promises = [];
  var d1, d2, d3;
  
  d1 = new $.Deferred();
  promises.push(d1.promise());
  setTimeout(function() {
    snippet.log("Resolving d1");
    d1.resolve(1);
  }, Math.floor(Math.random() * 1000));
  
  d2 = new $.Deferred();
  promises.push(d2.promise());
  setTimeout(function() {
    snippet.log("Resolving d2");
    d2.resolve(2);
  }, Math.floor(Math.random() * 1000));
  
  d3 = new $.Deferred();
  promises.push(d3.promise());
  setTimeout(function() {
    snippet.log("Resolving d3");
    d3.resolve(3);
  }, Math.floor(Math.random() * 1000));
  
  return $.when.apply($, promises);
}

// Use it
doSomething().then(function() {
  snippet.log("All resolved");
});
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Upvotes: 1

Related Questions