1
//I have the following function:
    function handle_message(msg)
    {
        //do work
        console.log('some work: '+msg.val);
        //call next message
        msg.next();
    }

//And array of message objects:
var msgs = [ {val : 'first msg'}, { val : 'second msg'}, { val : 'third msg'}];

//I link messages by setting next parameter in a way that it calls handle_message for the next msg in the list. Last one displays alert message.
msgs[2].next = function() {alert('done!')};
msgs[1].next = function() {handle_message(msgs[2]);};
msgs[0].next = function() {handle_message(msgs[1]);};

//Start the message handle "chain". It works!
handle_message(msgs[0]);

//======== Now I do exactly the same thing but I link messages using the for loop:

for (var i=msgs.length-1; i>=0; i--)
{
    if (i==msgs.length-1)
    {
        msgs[i].next = function() {alert('done!');};
    }
    else
    {
        msgs[i].next = function() {handle_message(msgs[i+1]);};
    }
}

//Start the message handling chain. It fails! It goes into infinite recursion (second message calls itself)
handle_message(msgs[0]);

Can sombody explain why it happens? Or maybe an alternative to this pattern? My case is this: I receive an array with messages and I have to handle them in order, one ofter another SYNCHRONOUSLY. The problem is some of the messages require firing a series of animations (jqwuery animate() which is async) and the following messages cannot be handled until the last animation is finished. Since there is no sleep() in javascript I was trying to use such pattern where the message calls the next one after it is finished (in case of animations I simply pass the 'next' function pointer to animate's "complete" callback). Anyway, I wanted to build this 'chain' dynamically but discovered this strange (?) behaviour.

4
  • you should really be using classes for this, objects with a prototype. Commented Apr 17, 2011 at 19:49
  • 2
    This is a very bad question title. In the future, please try to summarize the results you are seeing that confuse you, or the results you are trying to achieve. You might as well post a question titled "I'm confused" or "Help!". Commented Apr 17, 2011 at 19:56
  • You're right, I ve changed it. Commented Apr 17, 2011 at 20:05
  • possible duplicate of Javascript closure inside loops - simple practical example Commented Jan 17, 2013 at 17:52

3 Answers 3

3

You need a closure to make it work:

function handle_message( msg ) {
    console.log( 'some work: ' + msg.val );
    msg.next();
}

var msgs = [{val :'first msg'},{val:'second msg'},{val:'third msg'}];

for ( var i = msgs.length - 1; i >= 0; i-- ) {
    (function(i) {
        if ( i == msgs.length - 1 ) {
            msgs[i].next = function() { alert( 'done!' ); };
        } else {
            msgs[i].next = function() { handle_message( msgs[i + 1] ); };
        }
    })(i);
}

handle_message( msgs[0] );

Live demo: http://jsfiddle.net/simevidas/3CDdn/

Explanation:

The problem is with this function expression:

function() { handle_message( msgs[i + 1] ); }

This function has a live reference to the i variable. When this function is called, the for loop has long ended and the value of i is -1. If you want to capture the current value of i (the value during the iteration), you need to an additional wrapper function. This function captures the current value of i permanently (as an argument).

Sign up to request clarification or add additional context in comments.

3 Comments

...or you could just use the closure on the latter function, where it's needed.
@Phrogz You mean inside the else-branch?
@ŠimeVidas Yes; your answer is just as valid, just commenting for the OP. Also, note that the value is actually -1 when the loop has finished not 0.
2

I think the problem is that i doesn't have the value you think it has:

// i is defined here:
for (var i=msgs.length-1; i>=0; i--)
{
    if (i==msgs.length-1)
    {
        msgs[i].next = function() {alert('done!');};
    }
    else
    {
        msgs[i].next = function() {
            // when this line gets executed, the outer loop is long finished
            // thus i equals -1
            handle_message(msgs[i+1]);
        };
    }
}

See point #5 Closures in loops at http://blog.tuenti.com/dev/top-13-javascript-mistakes/

Comments

1

Think about the values you are capturing in the closure.

msgs[i].next = function() {handle_message(msgs[i+1]);};

This captures the value of i, but it changes the next iteration so you get an infinite loop.

By the end of the loop i is -1 so i+1 is going just going to be the same message over and over again.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.