1

I'm currently building a simple lightbox for my website using jquery.deferred patterns and while doing so I'm trying to maintain a flat chain of asynchronous operations and avoid the infamous nested callback pyramid of doom. Here is what I have so far:

$('#gallery a').click( function() {

    var id = $(this).attr('id');    // Id of the current selection
    var current = 0;                // Array index of the current item

    getTpl()                        // Get and load lightbox template
        .then(getData)              // Get JSON data of the selected item
        .then(getItem)              // Pass JSON data and get the item
        .then(loadItem)             // Use JSON data to create the HTML output
        .done(function() {
            // Wrap everything up, intialize the lightbox controls
            // (requires the JSON data) and load the lightbox
        });
});

The first two steps (getTpl, getData) are simple AJAX promises and work fine. I can also pass the JSON data further down the chain to the third step (getItem). That's where the problems start:

  1. I have to pass the variables 'id' and 'current' from the higher scope to the functions that are called during the chain (getItem and loadItem use these variables to construct URLs and create HTML output). How do I do that without breaking the asynchronous chain?
  2. Do I really have to return my JSON data (and other variables) from every function along the way to use it further down the chain? Isn't there an easier way to keep the data and necessary variables available throughout the entire chain?
  3. Can I throw random operations into the chain, e.g. a simple jquery fadeIn? That's what '.done' is for, because '.then' expects a new promise, right?

Thanks in advance!

sverebom
  • 13
  • 4

1 Answers1

2

If you do it like this:

var id = $(this).attr('id');    // Id of the current selection
var current = 0;                // Array index of the current item

getTpl()                        // Get and load lightbox template
    .then(getData)              // Get JSON data of the selected item
    .then(getItem)              // Pass JSON data and get the item
    .then(loadItem)

Then, you are leaving it to the promise system to set up the arguments for your next function in the chain. Since the promise system is going to use the return from the previous operation in the chain, if you want to pass arguments through to all of these, then all functions have to participate in passing them on. With this exact structure, there is no way around it.

For more general approaches to sharing data with the chain, see:

How to chain and share prior results with Promises

A common scheme for passing an arbitrary number of variables through like this is to put them on an object and just have each function accept and object and return that object.

var options = {
    id: $(this).attr('id'),     // Id of the current selection
    current: 0                  // Array index of the current item
};

getTpl(options)                 // Get and load lightbox template
    .then(getData)              // Get JSON data of the selected item
    .then(getItem)              // Pass JSON data and get the item
    .then(loadItem)

Then, each function getTpl(), getData(), getItem() and loadItem() can expect their first argument to be this option object and they should all resolve with that options object. They are each free to add new properties onto the object that will be passed on through the chain.


You do have some other options for how to structure it. If your functions are inline, then they can directly access part variables within scope:

var id = $(this).attr('id');    // Id of the current selection
var current = 0;                // Array index of the current item

getTpl()                        // Get and load lightbox template
    .then(function() {
        // code here can directly access current and id variables
    }).then(...)

Or, you can call your functions directly and pass them whatever variables they need:

var id = $(this).attr('id');    // Id of the current selection
var current = 0;                // Array index of the current item

getTpl()                        // Get and load lightbox template
    .then(function() {
        return getData(id, current);
    }).then(function(data) {
        return getItem(data, id);
    }).then(function(item) {
        return loadItem(item, current);
    });
Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I already figured/remembered that I could just declare an object that holds my variables and that I can feed with further variables as I push it through the chain. Stupid me! The reason why I split the operations into seperate functions is because these operations can be called from different places, and depending on the context the chain might change (with random methods thrown into the chain). I thought it would be a good approach to have a separate functions and chain them together as needed depending on the context. If you have a smarter approach to handle this, please let me know. – sverebom May 25 '16 at 18:53
  • @user3330116 - I've offered you three separate approaches already. I'd personally probably pick the options object passed on through as that is the most flexible. Each function will have it's required/expected properties on that object that can be used in this environment or when called separately. But, it gives you the flexibility of passing other properties through when chaining. – jfriend00 May 25 '16 at 19:01
  • That's what I have done now, and it works like charm. The "pass an object through the chain" approach was the key. Clean and simple. Thanks for reminding about the general use of objects in JS and the additional information about the work flow of Promises. – sverebom May 25 '16 at 19:55
  • @user3330116 - Since it appears you may be relatively new here, if this provided an answer to your question, then you can click the green checkmark to the left of the answer. This will indicate to the community that your question has been answered and you will earn some reputation points for following the proper procedure. – jfriend00 May 25 '16 at 19:58