4

I am looking for a way to call a function only after .each() finishes its execution. In the example below, how to make sure that postPreparation() runs immediately after $('.element').each() completes?

$('.element').each(function() {
  /** 
   * 'prepareLayer()' is a complex function that takes a while to complete and,
   *  as in this construct, needs to be invoked for each matched element. Basically,
   * 'prepareLayer()' adds a lot of new HTML elements to the page.
   */   
  prepareLayer();
});

/**
 * Ideally, this should immediately run _after_ the above function completes
 * i.e. after each '.element' finishes running prepareLayer().
 *
 * 'postPreparation()' needs to attach some event handlers for the new HTML elements
 * created in 'prepareLayer()'.
 */
postPreparation();

Technically, I am looking for a way to invoke a callback function for .each().

NOTE: I just confirmed, in the example above, that postPreparation() will execute only after .each() completes. The problem was my prepareLayer() builds the new HTML elements using AJAX, so each() returns prematurly. As suggested by @Alnitak, an asynchronous AJAX request wouldn't stop .each() from returning prematurely.

moey
  • 10,587
  • 25
  • 68
  • 112

3 Answers3

9

Unless prepareLayer() is doing something asynchronous (e.g. AJAX, or animation) each pass around the loop can't terminate until prepareLayer() has finished anyway and your code will already do what you want.

FWIW, if there are no additional operations or parameters in your existing .each loop you actually just need to write this:

$('.element').each(prepareLayer);

i.e. there's no need for the additional anonymous function wrapper.

On the other hand, if it's doing something asynchronous, use deferred objects:

var def = [];
$('.element').each(function() {
    // have prepareLayer return a _promise_ to return
    def.push(prepareLayer());
});

function prepareLayer() {
    var jqxhr = $.get(..., function() {
        // do stuff with content
    });
    return jqxhr;
}

// use "when" to call "postPreparation" once every
// promise has been resolved
$.when.apply($, def).done(postPreparation);
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • `prepareLayer()` has lots of `.append(/* new
    */)` and related calls to add new HTML elements; `postPreparation()` is for attaching some event handlers for those new HTML elements.
    – moey Jan 28 '12 at 08:54
  • @Siku-Siku.Com No, `.append()` is a blocking (synchronous) call, so your function won't return until it actually finished. – Alnitak Jan 28 '12 at 08:55
  • @Alnitak how would an AJAX request stop .each( ) from returning? Ajax requests are given callbacks to be executed when requests are finished. – Nadir Muzaffar Jan 28 '12 at 09:33
  • @NadirMuzaffar an _asynchronous_ AJAX request **wouldn't** stop `.each()` from returning prematurely, hence my second example where the `jqXHR` object returned by `$.ajax()` could be returned by `prepareLayer()` – Alnitak Jan 28 '12 at 09:38
  • @Alnitak "Unless prepareLayer() is doing something asynchronous (i.e. AJAX, or animation) the loop won't terminate until each operation"? What exactly are you saying if not that an ajax request inside prepareLayer would stop .each() from returning? – Nadir Muzaffar Jan 28 '12 at 09:44
  • @NadirMuzaffar I'm saying the exact opposite - if you start an sync AJAX request inside `.each()`, the `.each()` _will_ terminate before they've all completed. – Alnitak Jan 28 '12 at 09:47
  • @Alnitak +1 and accepted answer. Maybe I was doing something wrong, but http://labs.siku-siku.com/experiment/03/index.solution.html doesn't seem to work (even with deferred object). – moey Jan 28 '12 at 10:33
  • @NadirMuzaffar yes, of course I know - I didn't change the meaning of my text, just rewrote it to make it clearer for you. – Alnitak Jan 28 '12 at 13:59
  • @Siku-Siku.Com yes, you need to return a deferred object in your callback - in this case the result of your `$.get()` call will suffice. – Alnitak Jan 28 '12 at 14:01
0

Use jquery promise:

$('.element').promise().done(function() {
    postPreparation();
});
0

I would wrap the call to postPreperation into some kind of counter object.

For example:

function createEvent(numOfSignals, callback) {
    var _event = {};

    _event.signal = function() {
        if(numOfSignals > 1) {
            numOfSignals--;
        }
        else {
            callback();
        }
    };

    return _event;
}

var event = createEvent(numOfPreperations, postPreperation);

Then inside prepareLayer, I'd call event.signal(). If numOfSignals is 1, then postPreperation will be called immediately.

You'll want to refine this, but the essential idea should work. You can check out a example of the idea here.

Nadir Muzaffar
  • 4,772
  • 2
  • 32
  • 48
  • Do you mean that it needs to be refined? As I mentioned already? More important, however, is that, since the idea is simple, the solution is more easily understood by providing a rudimentary implementation. – Nadir Muzaffar Jan 28 '12 at 09:25
  • I mean because it doesn't in any way handle iterating over a set of HTML elements. – Alnitak Jan 28 '12 at 09:36
  • The cusp of the problem is executing a function after a set of other functions have completed. Now since Javascript is usually a single thread, this isn't a problem unless async methods are simulated with setTimeouts. This solution is more generic for people that face the same fundamental problem. Failing to generalize is usually bad. Where would we be without MapReduce ;) – Nadir Muzaffar Jan 28 '12 at 09:41
  • If you had threads, you could use your solution. If you've got async methods, you should use the tools provided, e.g. deferred objects. – Alnitak Jan 28 '12 at 09:45
  • Deferred objects is a JQuery construct. The rudimentary solution I gave follows the same basic idea. Also I'm not sure how threads/async applies since I already explained there's usually no such thing in Javascript, only simulated. Please avoid another statement that goes on the lines of a "bad implementation". I've already explained the benefits of giving a rudimentary example. – Nadir Muzaffar Jan 28 '12 at 09:50