Categories:

Interrupting a function safely

In JavaScript1.4, new methods for error-handling are introduced (throw, catch). JavaScript1.5 introduces a new object constructor, the Error() constructor, which we theoretically can use to define our own error codes. But neither of these are currently available to Internet Explorer users... and even diehard Netscape fans (like yours truly) find no simple exit to a function. Let's review our ineq function again:

function ineq(arg1,sign,arg2) {
    var temp = 0
    var temp3 = 0
    if (arguments.length%2==1) {
        var temp2 = false
        } else {
        var temp2 = true
        }
    var ineqs = new Array()
    for (temp = 0; (temp2==true)&&(temp < arguments.length); temp++) {
        if (temp%2 == 0) {
            ineqs[temp] = arguments[temp]
            if (ineqs[temp][0]=="NaN") {
                temp2 = false
                }
            } else {
            ineqs[temp] = arguments[temp]
            temp2 = false 
            for (temp3 = 0; temp3 < INEQOP.length; temp3++) {
                if (INEQOP[temp3]==ineqs[temp]) {
                    temp2 = true
                    }
                }
            }
        }
    for (temp = 1; (temp < ineqs.length)&&(temp2 == true); temp+=2) {
        temp2 = false
        if ((ineqs[temp]==LT)&&(ineqs[temp-1].isLesser(ineqs[temp+1]))) {
            temp2 = true
            }
        if ((ineqs[temp]==EQ)&&(ineqs[temp-1].isEqual(ineqs[temp+1]))) {
            temp2 = true
            }
        if ((ineqs[temp]==GT)&&(ineqs[temp-1].isGreater(ineqs[temp+1]))) {
            temp2 = true
            }
        if ((ineqs[temp]==LTE)&&(!ineqs[temp-1].isGreater(ineqs[temp+1]))) {
            temp2 = true
            }
        if ((ineqs[temp]==IEQ)&&(!ineqs[temp-1].isEqual(ineqs[temp+1]))) {
            temp2 = true
            }
        if ((ineqs[temp]==GTE)&&(!ineqs[temp-1].isLesser(ineqs[temp+1]))) {
            temp2 = true
            }
        }
    return temp2
    }

Ideally, once temp2 becomes false outside a for-loop, there should be no need to execute any other statements. Still, it does evaluate each follow-up for-loop's initial line once. This is not entirely efficient. Imagine what happens when we render the flag false early in a much larger validation. It still goes through a lot of statements.

The break statement can help within a loop to abort the loop. The radioval() function from the previous lesson can be altered here:

function radioval(obj) {
    var flag = -1
    for (var k = 0; k < obj.length; k++) {
        if (obj[k].checked) {
            flag = k
            break;
            }
        }
    return flag
    }

In the spirit of flags, I could just as easily have used:

function radioval(obj) {
    var flag = -1
    for (var k = 0; (k < obj.length)&&(flag==-1); k++) {
        if (obj[k].checked) {
            flag = k
            }
        }
    return flag
    }

But there is a more advanced way to exit a function in a controlled manner, and even to provide a central area for reporting errors to the user. Labels can be used to designate a loop, and a break statement can be used to exit just after the loop. Imagine this scenario for the radioval() function:

function radioval(obj) {
radioval_func:
    for (var radioval_loop = 1; radioval_loop < 2; radioval_loop++) {
        var flag = -1
        for (var k = 0; k < obj.length; k++) {
            if (obj[k].checked) {
                flag = k
                break radioval_func;
                }
            }
        }
    return flag
    }

At first, you might wonder about the new outer loop -- after all, it will only run once. But the break statement breaks all loops out to the radioval_loop loop. Nothing between the break statement and the rest of the outer loop will execute. So this "useless" loop allows us to control the flow of the function from inside the inner loops.

Try this demo:

0 1 2 3 4

Of course, breaking the outer loop doesn't help us much in the radioval() function. But in the larger, and more complex ineq() function...

function ineq(arg1,sign,arg2) {
ineq_func:
    for (var ineq_loop = 1; ineq_loop < 2; ineq_loop++) {
        var temp = 0
        var temp3 = 0
        if (arguments.length%2==1) {
            var temp2 = false
            break ineq_func;
            } else {
            var temp2 = true
            }
        var ineqs = new Array()
        for (temp = 0; (temp2==true)&&(temp < arguments.length); temp++) {
            if (temp%2 == 0) {
                ineqs[temp] = arguments[temp]
                if (ineqs[temp][0]=="NaN") {
                    temp2 = false
                    break ineq_func;
                    }
                } else {
                ineqs[temp] = arguments[temp]
                temp2 = false 
                for (temp3 = 0; temp3 < INEQOP.length; temp3++) {
                    if (INEQOP[temp3]==ineqs[temp]) {
                        temp2 = true
                        }
                    }
                }
            }
        if (temp2 == false) {
            break ineq_func;
            }
        for (temp = 1; (temp < ineqs.length)&&(temp2 == true); temp+=2) {
            temp2 = false
            if ((ineqs[temp]==LT)&&(ineqs[temp-1].isLesser(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==EQ)&&(ineqs[temp-1].isEqual(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==GT)&&(ineqs[temp-1].isGreater(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==LTE)&&(!ineqs[temp-1].isGreater(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==IEQ)&&(!ineqs[temp-1].isEqual(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==GTE)&&(!ineqs[temp-1].isLesser(ineqs[temp+1]))) {
                temp2 = true
                }
            }
        }
    return temp2
    }

I've added three break ineq_func; statements. In the design of this particular function, designed to verify compound inequalities (a < x < b <...) these breakpoints do not really save much time. But these breakpoints indicate invalid arguments, whereas the final loop performs the actual inequality test. Wouldn't it be nice to include some sort of error message for an invalid function call?

function ineq(arg1,sign,arg2) {
var ineq_err = 0
ineq_func:
    for (var ineq_loop = 1; ineq_loop < 2; ineq_loop++) {
        var temp = 0
        var temp3 = 0
        if (arguments.length%2==1) {
            var temp2 = false
            ineq_err = 1
            break ineq_func;
            } else {
            var temp2 = true
            }
        var ineqs = new Array()
        for (temp = 0; (temp2==true)&&(temp < arguments.length); temp++) {
            if (temp%2 == 0) {
                ineqs[temp] = arguments[temp]
                if (ineqs[temp][0]=="NaN") {
                    temp2 = false
                    ineq_err = 2
                    break ineq_func;
                    }
                } else {
                ineqs[temp] = arguments[temp]
                temp2 = false 
                for (temp3 = 0; temp3 < INEQOP.length; temp3++) {
                    if (INEQOP[temp3]==ineqs[temp]) {
                        temp2 = true
                        }
                    }
                }
            }
        if (temp2 == false) {
            ineq_err = 3
            break ineq_func;
            }
        for (temp = 1; (temp < ineqs.length)&&(temp2 == true); temp+=2) {
            temp2 = false
            if ((ineqs[temp]==LT)&&(ineqs[temp-1].isLesser(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==EQ)&&(ineqs[temp-1].isEqual(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==GT)&&(ineqs[temp-1].isGreater(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==LTE)&&(!ineqs[temp-1].isGreater(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==IEQ)&&(!ineqs[temp-1].isEqual(ineqs[temp+1]))) {
                temp2 = true
                }
            if ((ineqs[temp]==GTE)&&(!ineqs[temp-1].isLesser(ineqs[temp+1]))) {
                temp2 = true
                }
            }
        }
    switch (ineq_err) {
        case 0 :
            break;
        case 1 :
            var errmsg = "Incorrect formatting in ineq: "
            errmsg+="\nRequires odd number of arguments.\na < x < b"
            alert(errmsg)
            break;
        case 2 :
            var errmsg = "Incorrect formatting in ineq: "
            errmsg+="\nNumber.NaN cannot be compared in inequality."
            alert(errmsg)
            break;
        case 3 :
            var errmsg = "Incorrect formatting in ineq: "
            errmsg+="\nRequires valid inequality code"
            errmsg+="\nLT <, EQ ==, GT >, LTE <=, IEQ !=, GTE >="
            alert(errmsg)
            break;
        default : 
            alert("Unscheduled error in ineq.")
            break;
        }
    return temp2
    }

In case you're wondering, the ineq_err variable is another flag. This one is used to detect errors the developer sets in place. The switch statement, covered in another tutorial at JavaScript Kit, enables us to then report errors in a controlled manner at the end of the function. By using the break statements earlier, we ensure it goes directly from the error to the error message, and then to the end of the function.

The system above has the following key features:

In conclusion, I wish I had discovered this sooner. Another function of mine, used for division of numbers with lots of digits, gets stuck in an endless loop if a particular condition occurs. It's extremely deep within the function -- trapped within about three or four for-loops. I've worked very hard to prevent that condition from happening, which is something internal to the function (a bug). Because of the validation requirements already in place, it cannot occur from a user or developer feeding bad arguments into it. I'll be revising that particular function soon (along with several others) to take advantage of these cross-browser features Real Soon Now (meaning when I get around to it) smile.gif (93 bytes)