0

EDIT - Js fiddle link to script file - http://jsfiddle.net/r4uH3/

EDIT 2 RE ACCEPTED ANSWER Although question already closed, thought I would add some detail on why I accepted the answer below.

Also see this re why original code didn't work - How do I return the response from an asynchronous call? - it's points re AJAX are not literally related, but the explanation of asynchronicity is important to understand.

Fabrício Matté's answer works perfectly for me, although I adapted it slightly:

(function($){
    // some pre-iteration stuff here

    // iteration vars
    var elementIndex = 0;
    var collectionLength = this.size();
    var ts = this;

    // THIS IS THE KEY BIT AS PER ACCEPTED ANSWER
    // RATHER THAN USING THE NORMAL "this.each"
    (function initLoop(){

        // check if got to last element
        if (elementIndex < collectionLength){

            // DO STUFF, WHATEVER, AS LONG AS YOU DON'T EXPECT
            // ASYNCHRONOUS FUNCTIONS LIKE AJAX / TIMERS NOT TO, WELL, EXECUTE ASYNCHRONOUSLY UNLESS YOU HANDLE THEM PROPERLY!!


            // AND FINALLY - GO TO NEXT ELEMENT IN COLLECTION
            initLoop();
        };

    })());
})(jQuery);

The other thing that helped, although not exactly related, was using global and element-specific variables stored using jQuery(el).data(); rather than using window.VARIABLENAME or MyNamespace.VARIABLE_NAME. Eg:

// outside of iteration
jQuery(window).data("GLOBAL_STUFF", { /* add properties here and set them later*/ });
var globalData = jQuery(window).data("GLOBAL_STUFF");

// inside iteration
jQuery(currentElement).data("ELEMENT_DATA", { /* add properties here and set them later*/ });
var elementData = jQuery(window).data("ELEMENT_DATA");

// then set props like so (obviously get, similarly..)
globalData.someArrayOfSomething.push(something);
elementData.someBooleanValue = true;

Again, thanks to Fabrício.


  1. I have written a jQuery plugin that, like most, can be executed on multiple (i.e. a collection of) elements.

  2. in the this.each(function(i,el){ }); part of the function, I create a new instance of another object type (nothing to do with jQuery) and call its "Init" method.

  3. I expect, with the .each loop, that it will loop to the next instance after the init method has been fully executed.

  4. I am not using any async (AJAX / timers) anywhere.

  5. I am using callbacks always on anything like "jQuery.fadeIn" or similar.

THE PROBLEM

The Init methods are called virtually in parallel. They do not complete their execution before the next one is called.

Can anybody advise of any known issues? Is there something I'm missing from the above "theory"?

Using jQuery 2.0.

Community
  • 1
  • 1
LiverpoolsNumber9
  • 2,384
  • 3
  • 22
  • 34
  • 3
    Not enough data. If you're dealing with async code, you should at least post a [SSCCE](http://sscce.org/). Without code we can do nothing but advise to adapt your object's `init` function to take a callback parameter or return a deferred instance. – Fabrício Matté Apr 25 '13 at 20:27
  • @FabrícioMatté I appreciate that but the amount of code is enormous. The init method is fairly long too. It does already take a callback. Also, could you explain what you mean by "return a deferred instance"? – LiverpoolsNumber9 Apr 25 '13 at 20:32
  • If you just want to know what code is being run in what order, that's what the browser's JavaScript debugger is for. – Blazemonger Apr 25 '13 at 20:34
  • Hence I suggested a [SSCCE](http://sscce.org/). `=]` Well yes, take a look at [`$.Deferred`](http://api.jquery.com/jQuery.Deferred/) - basically you create a `Deferred` instance inside `init` and return it so your caller can bind `done` handlers to it. Of course you will have to resolve the deferred somewhere after `init` has finished. – Fabrício Matté Apr 25 '13 at 20:34
  • Just use a bog standard for loop, if you want it to be synchronous. Not very pretty though. .each is asynchronous for a reason. Mainly so that the UI thread doesn't become unresponsive. jQuery is doing best practise. Your anonymous function is being called asynchronously. – twilson Apr 25 '13 at 20:35
  • @FabrícioMatté about to put a js fiddle link up... – LiverpoolsNumber9 Apr 25 '13 at 20:35
  • @twilson Are you saying that `.each` is asynchronous? Sorry to disappoint but it is a completely synchronous method. What is async is the function that OP calls from inside of `.each`. – Fabrício Matté Apr 25 '13 at 20:36
  • @twilson he's right. That's why I'm tearing my hair out!! – LiverpoolsNumber9 Apr 25 '13 at 20:37
  • 2
    @LiverpoolsNumber9 The fact is javascript executes synchronously (with a few exceptions). If you are seeing otherwise, that means you are using one of the methods that are an exception to that rule, such as a setTimeout or an ajax request. We can't see your code, therefore there isn't much we can do to help you figure out why you are seeing otherwise. – Kevin B Apr 25 '13 at 20:38
  • @KevinB I'm not I promise!! See js fiddle link! – LiverpoolsNumber9 Apr 25 '13 at 20:38
  • Ah, sorry i didn't see the fiddle. – Kevin B Apr 25 '13 at 20:39
  • Where within that fiddle do we need to look to see the loop you are performing that's supposed to be synchronous? The commented out section? or the `$.fn.jrte` method – Kevin B Apr 25 '13 at 20:41
  • @KevinB At the end. The actual jQuery "plugin" function. – LiverpoolsNumber9 Apr 25 '13 at 20:42
  • Oh wait I've found the `Init` function. Though it looks like quite a stack of callbacks. You may be able to resolve a deferred or run a callback inside the last callback in that stack, though it is hard to say whether it will work without an example that executes. – Fabrício Matté Apr 25 '13 at 20:46
  • Is the problem you are seeing them all fade in at the same time? or what. Based on the current code, i would expect them all to fade-in at the same time. – Kevin B Apr 25 '13 at 20:49

1 Answers1

2

Try replacing your for loop with:

var i = 0,
    l = window.JRTE_INSTANCES.length;
(function initloop() {
    if (i < l) window.JRTE_INSTANCES[i].Init(initloop);
    i++;
}());

This will start the init loop by calling window.JRTE_INSTANCES[0].Init. The initloop passed as callback will execute again when the Init concludes, starting another Init with the next i and so forth until it has iterated over all instances.

Here's a more practical async demo using a very similar structure as the above: Fiddle

Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166
  • I didn't test it but I believe the logic is correct. Should work as long as the callback system in place suffices. – Fabrício Matté Apr 25 '13 at 20:57
  • 1
    Looks correct; the problem in code seems to be that startEditor contains async code (namely, jQuery.fadeIn). So with the code in the original fiddle, all the Init calls get made before even the first fadeIn completes. Making the next Init call inside the callback to the previous one sounds like what the OP wants. – StackExchange saddens dancek Apr 25 '13 at 21:05
  • @dancek Yes, I noticed that when Kevin commented in the question too. You explained it better though, thanks for the addition. `=]` – Fabrício Matté Apr 25 '13 at 21:05
  • @dancek but I'm waiting for the fade to finish executing? – LiverpoolsNumber9 Apr 25 '13 at 21:09
  • @FabrícioMatté thanks so much for taking the time to answer. Late here but will look in the morning :) – LiverpoolsNumber9 Apr 25 '13 at 21:09
  • I'll add that this code seems a bit smelly to me; I'd rather make it: `(function initloop(n) { ... .Init(function() {initloop(n+1)}); }(0));`. Though this might be a matter of taste (the inline function is ugly, too). – StackExchange saddens dancek Apr 25 '13 at 21:10
  • @dancek Yeah, personal taste. I find lexical scope easier to read than traditional recursion as yours, but both have the same effect. `=]` – Fabrício Matté Apr 25 '13 at 21:15
  • @FabrícioMatté Have accepted as answer (even though post closed) as you are correct in everthing you say. I have to say I'm a little disappointed that the question has been closed as, upon further investigation, and with your help, I've found out what the issue was. However, I won't share it here now. Shame really, could've been really helpful to somebody in the future which, after all, is what this site is about. – LiverpoolsNumber9 Apr 26 '13 at 08:11
  • @LiverpoolsNumber9 Yes, you can't remove a close vote once you've cast it (which I've cast before your updated question with fiddle), though I believe you can still edit the question to include the data. `=]` I'll cast a reopen vote nevertheless, but in case you want to edit the question, I'd suggest adding the `Init` method's code too so it doesn't end up closed as Too Localized after it is re-opened. – Fabrício Matté Apr 26 '13 at 14:59
  • @FabrícioMatté Ok thanks. Your "closure" / "recursive" type solution is perfect by the way! Would never have thought of that in a million years! But still don't understand why "this.each" flies through the collection without completing the "init" function! Ah well.. – LiverpoolsNumber9 Apr 26 '13 at 16:39
  • @LiverpoolsNumber9 As `Init` is asynchronous, `.each()` will call all `Init`s without waiting for these to finish. Here's another popular better explained async code explanation: http://stackoverflow.com/q/14220321/1331430 – Fabrício Matté Apr 26 '13 at 16:57
  • 1
    @FabrícioMatté ok thanks. Have updated above, hope it's a bit more helpful now!! – LiverpoolsNumber9 Apr 26 '13 at 16:58