0
\$\begingroup\$

I've two versions of promise code. The page is a simple one - an unordered list with a few items. I am trying to make the items show up (fadeIn) one-after-another sequentially. This exercise is done to sort of explain people what promises are...

Version 1:

$('li').hide();

var fadeInFunc = function (obj) {
    var deferred = $.Deferred();
    obj.fadeIn('slow', function () {
        deferred.resolve();
    });
    return deferred.promise();
};

var funcWrapper = function (obj) {
    return function(){
        return fadeInFunc(obj);
    };
};

$('input').click(function () {
    fadeInFunc($('#li1'))
        .then(funcWrapper($('#li2')))
        .then(funcWrapper($('#li3')));
});

Version 2:

$('li').hide();

var fadeInFunc = function (obj) {
    var deferred = $.Deferred();
    obj.fadeIn('slow', function () {
        deferred.resolve();
    });
    return deferred;
};

var execute = function () {
    return fadeInFunc(this);
};

$('input').click(function () {
    fadeInFunc($('#li1'))
        .then(execute.bind($('#li2')))
        .then(execute.bind($('#li3')));
});

Both work fine. However, I want someone to dive in and try to understand the code better. Moreover, this code becomes unmanageable when you have variable number of li elements.

Here is the version for variable number of li elements:

var liCount = Math.floor(Math.random() * 6) + 1;
$('p').html('liCount: ' + liCount);
var ul = $('ul');
for(var i = 0; i< liCount; i++){
    ul.append('<li>Item ' + (i+1) + '</li>');
}
var liArray = $('li').hide();

var fadeInFunc = function (obj) {
    var deferred = $.Deferred();
    obj.fadeIn('slow', function () {
        deferred.resolve();
    });
    return deferred;
};

var funcWrapper = function(o){
    return function() { return fadeInFunc(o); };
};

$('input').click(function () {
    var prev;
    liArray.each(function(idx, obj){
        var o = $(obj);
        if(idx === 0){
            prev = fadeInFunc(o);
        }
        else{
            prev = prev.then(funcWrapper(o));
        }
    });
});
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You don't actually need promises for this one. Here's an overly simplified version that takes advantage of the fact that you can call the containing function by a given name.

// Let's turn a jQuery set to an array coz it doesn't have shift, which we'll use
var liArray = $('li').hide().get();

$('input').click(function handle(){
  // Unqueue one from the set and fade it in. Note that shift() alters the array so
  // the next time we call it, the second one is in front of the queue. Also wrapping
  // what we shifted with jQuery because get() returns an array of DOM elements of 
  // the jQuery set
  var li = $(liArray.shift());

  // If we shifted out nothing, then that's the end of this.
  if(!li) return;

  // Set the callback of the fadeIn to the same function. That way, if the animation
  // completes, then it fires again.
  li.fadeIn(handle);
});

If the idea is to teach promises, then the following might be better. Here's an interesting approach where we use Array.prototype.reduce to carry over a dummy deferred, attach the handlers and kick it off, starting the fadeIn process.

var liArray = $('li').hide().get();

// So here we create a deferred object and during its creation, fade in the element.
// When the animation completes, it calls on `deferred.resolve` which resolves the
// fade promise.
function fade(item) {
  return $.Deferred(function (deferred) {
    $(item).fadeIn(deferred.resolve);
  }).promise();
}

// Here we use reduce to attach a handler for each item as well as allow us to carry
// over a value to the next iteration - the result value of `then()`.
function reducer(promise, item) {
  return promise.then(function () {
    return fade(item);
  });
}


// Here we create a dummy promise which allows our reducer to attach the handlers. 
// Then we use reduce to attach a handler for each item on the promise and then kick
// of the whole operation by resolving the dummy promise.
$('input').click(function handle() {
  var root = $.Deferred();
  liArray.reduce(reducer, root.promise());
  root.resolve();
});

Both examples assume liArray is an array containing li elements, whether dynamically generated or just fetched from the page, either way it doesn't matter as long as they're already rendered. Also went for an array instead of a jQuery object because array operations like shift() and reduce() are very handy.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.