ActiveBlog

When ++ can be Harmful in JavaScript
by

, May 26, 2006

I've been busy writing JavaScript lately, of all things. It's definitely a lot more fun to use now than in, say, the good old days of 1997, when most of us treated it like a Java-like language for making sure...

I've been busy writing JavaScript lately, of all things.  It's definitely a lot more fun to use now than in, say, the good old days of 1997, when most of us treated it like a Java-like language for making sure no one was entering phone numbers with hyphens in them, before submitting the form to the CGI server.  It's especially more interesting when you can assume you're targetting a modern, compliant implementation.  I recommend Firefox. One day I'll write up the fun of using e4x, which is by far the best way of embedding XML in programming languages I've seen so far.

This time I want to talk briefly about a problem I ran into.  I needed to carry out a basic select-and-map  copy of one array to another, where the map operation can throw an exception.  I tried using this code:

function f1() { return 1; }
function f2() {throw "bad call";}
const functions = [f1, f2];
const a = [0, 2, 4, 5, 6];
   
function doTest1() {
   var b = [];
   var b_idx = 0;
   for (var a_idx = 0; a_idx < a.length; ++a_idx) {
      if (a[a_idx] > 2) { // First filter
         // Now do the map
         try {
            // When a_idx == 3 we'll throw an exception calling function f2
            b[b_idx++] = functions[a[a_idx] % 2]()
         } catch(ex) {
            // Assume we didn't increment b_idx, as the function call threw an exception
            b[b_idx++] = -1;
         }
      }
   }
   alert("[[" + b.join("], [") + "]]");
}

But when I ran it, JavaScript reported an undefined element at position 4, like so: [[1], [], [-1], [1]]

The problem is that at the line where I call function f2, which is going to throw an exception, JavaScript has already incremented b_idx.  Then it increments it again in the exception handler.  At first I thought I was relying on undocumented behavior, but in fact this behavior is mandated by ECMA-262, which calls for full evaluation of the left hand side of an assignment statement as a reference before evaluating the expression on the right.

The ++ and -- operators have an unarguable elegance about them.  But they create side-effects, and side-effects and exceptions are a troublesome mixture.  This code is slightly longer, but works.

function doTest2() {
   var b = [];
   var b_idx = 0;
   for (var a_idx = 0; a_idx < a.length; ++a_idx) {
      if (a[a_idx] > 2) { // First filter
         // Now do the map
         try {
            // When a_idx == 3 we'll throw an exception calling function f2
            b[b_idx] = functions[a[a_idx] % 2]()
         } catch(ex) {
            b[b_idx] = -1;
         }
         ++b_idx; // increment the target index outside the handler.
      }
   }
   alert("[[" + b.join("], [") + "]]");
}

Time once was, back in 1997 or so, when I got a message like 'undefined' at a web site I'd look at the source, or even accept IE's offer to debug and spend the next half hour in Visual Interdev looking at a call stack.  I still see an alert box or console message indicating an attempt to use an uninitialized variable once in a while.  Next time I'll look at the code to see if it's caused by a problem like this.

Subscribe to ActiveState Blogs by Email

Share this post:

Comments

3 comments for When ++ can be Harmful in JavaScript
Permalink

> The ++ and -- operators have an unarguable
> elegance about them.

I could argue the negative is that debate. :)

Permalink

Everything you have changed before the exception IS changed. This is how your code would have looked like if you didn’t use b_idx++:

try {
        b_idx = b_idx +1;
        b[ b_idx -1 ] = functions[a[a_idx] % 2]()
} catch(ex) {
        b_idx = b_idx +1;
        b[ b_idx -1 ] = -1;
}

Now consider this:

var foo="bar";
try {
foo="foobar";
        throw "Error";
}
catch (e) {
        alert(foo);
}

Since the variable foo is assigned the value “foobar” before the exception, you will get an alert box with the message “foobar”, not “bar”.
One have to remember that the variable you use ++ or – on IS CHANGED, and it DOES MATTER if you put the ++ or -- before (pre) or after (post) the variable name. Here is the pseudo code for post and pre increment:

var value=0;
function postincrement(){
        var temp = value;
        value = value + 1;
        return temp;
}

function preincrement(){
        value = value + 1;
        return value;
}

Now look at this example:

var b=[], b_idx=0;
b[b_idx++] = "some value";
alert(b[b_idx]);

In the example above the alert box will show “undefined”. Since b_idx is post incremented the string will be assigned to b[0] and the alert will read from b[1]. But if using pre increment instead:

b[++b_idx] = "some value";
alert(b[b_idx]);

it would have worked since the value is assigned and read from the same index. But since the variable is pre incremented, the value will be assigned and read from b[1] and you will never have a value assigned to b[0] unless you assign b_idx = -1; from the start.

But in your example I would have used the push method of the array instead, since you aren’t interested in what index each assignment has:

try {
        b.push( functions[a[a_idx] % 2]() );
} catch(ex) {
        // no nead to undo any assignments to the array
}

If the functions throws an exception nothing will be added to the array in the first place, so the code in the catch doesn’t have to undo an assignment. (If you still want the value to be -1 in case of a failure then put b.push(-1) in the catch statement).

The key benefits of using push instead are that the code is clearer, you don’t have to keep track of the current index, you don’t get any holes in the assignment and b.length will show how many elements you have assigned to the array.

Another thing you should know:

for (var a_idx = 0; a_idx < a.length; ++a_idx);

Should probably be written as:

for (var a_idx = 0, a_len = a.length; a_idx < a_len; ++a_idx)

The reason is because the middle statement is evaluated every time of the loop. As long as the variable you are testing against is constant, use it as a constant. If it isn’t constant you should use while/do-while instead.

I’m happy that we don’t have =+ and =-…. That was very harmful…

Permalink

I assume you realize I wrote that over two years ago.
My use of JS has changed since then, including using 'push' instead of indexing on assignment to build an array. And if I'm dealing with an array of objects, I prefer this idiom:

for (var item, idx = 0; item = coll[idx]; ++idx) {...

Supposedly it's faster than the other idioms, as long as coll[idx] never contains a value that evaluates to false in a boolean context.

On the other hand, I recently got burned with this JS 1.7 code:

function twoValue() { return [x, y] }

var newVar, key, a2, b2; ...

newVar = complexExpn + oldVar[key]
[a2, b2] = twoValue();

See why newVar ends up with an undefined value?