breaking out of a forEach ?

J

Jorge

Is it possible ?

`throw` will take you out of it. But then you might want a wrapper to
handle it gracefully, and it all adds complexity of course.

Here's an untested example:

Array.prototype.each = (function(){
   var stopIterationError = { };
   return function(callback, thisValue) {
     try {
       this.forEach(function() {
         if (callback.apply(thisValue, arguments) === false) {
           throw stopIterationError;
         }
       });
     }
     catch(err) {
       if (err !== stopIterationError) {
         throw err;
       }
     }
   };

})();

[1,2,3].each(function(item, index){
   console.log(arguments);
   if (index == 1) return false;

});

(Maybe you can replace that `stopIterationError` object with an object
that inherits from `Error`; I'm not sure how cross-browser it would be)

Great. I wanted to break out from a catch. So I just need to put the
try wrapping the forEach, right ?

e.g.

var n=0;
try {
[1,2,3].forEach(function (value, index, object){
n++;
throw({});
});
} catch (e) {
console.log("Broke out @#"+ n);
}

?
Thanks,
 
A

Antony Scriven

Is it possible [to break out of a forEach()]?

`throw` will take you out of it. But then you might want
a wrapper to handle it gracefully, and it all adds
complexity of course.

Here's an untested example:

Array.prototype.each = (function(){
var stopIterationError = { };
return function(callback, thisValue) {
try {
this.forEach(function() {
if (callback.apply(thisValue, arguments) === false) {
throw stopIterationError;
}
});
}
catch(err) {
if (err !== stopIterationError) {
throw err;
}
}
};

})();

[1,2,3].each(function(item, index){
console.log(arguments);
if (index == 1) return false;

});

Does it need to be that complex?

var LIB = (function(){
Array.prototype.each = function(func, thisvalue){
try{
this.forEach(func, thisvalue);
}catch(e){
if(e !== LIB.stopEach){
throw e;
}
}
};
return {stopEach: {}};
}());

[1,2,3].each(function(item, index){
console.log(arguments);
if (index == 1) throw LIB.stopEach;
});

IME this construct doesn't occur very often so I also think
it helps to make the termination statement as obvious as
possible. --Antony
 
J

Jorge

Is it possible ?

AFAIK, it isn't. With iterators, you could at least throw a
StopIteration, but I don't see any possibility with forEach().

You could try using something along the lines of:

    function walk (list, fun, context) {
        var i = 0,
            len = list.length,
            rv;
        for (; i < len; i++) {
            if (i in list) {
                rv = fun.call(context, list, i);
                if (rv !== undefined) {
                    return rv;
                }
            }
        }
    }

This works similar to Array.prototype.forEach, but will stop looping if
the function "fun" returns anything other than undefined. It can also
double as a filter to find the first array element matching your
criteria. Adding this to Array.prototype is straight-forward, but if
it's stand-alone, you can use it to iterate over node lists (for
example) as well as arrays.


Thanks, Stefan. I think I'm going to throw in order to break out.
Cheers,
 
A

Antony Scriven

[...]

Great. I wanted to break out from a catch. So I just need to put the
try wrapping the forEach, right ?

e.g.

var n=0;
try {
[1,2,3].forEach(function (value, index, object){
n++;
throw({});
});} catch (e) {

console.log("Broke out @#"+ n);

}

This would be my preferred overall form since it's intention
is obvious to the reader. However, if your real code is
anything more complex than n++ then you could potentially be
masking any other exceptions that get thrown. Make sure you
examine the exception object and check it's the right one as
Kangax did originally. --Antony
 
A

Antony Scriven

On 3/4/10 11:00 AM, Antony Scriven wrote:

[...]
var LIB = (function(){
Array.prototype.each = function(func, thisvalue){
try{
this.forEach(func, thisvalue);
}catch(e){
if(e !== LIB.stopEach){
throw e;
}
}
};
return {stopEach: {}};
}());

[...]

[...]

<digression>
In a retrospect, this whole idea of throwing wasn't that
good. In most common scenario, breaking out of the loop
was actually _not_ worth performance hit introduced by
try/catch. And since `each` was used everywhere
throughout the library (even in places where plain
`while` would suffice), it's easy to imagine how it all
hindered performance.
</digression>

Well, the try/catch is outside of the performance-critical
part of the loop (though nesting forEach() would clearly
change that). Though it will no doubt have an impact, I've
no idea how much of an impact it will have in actual
application code. I wouldn't be surprised if other things,
either inside or outside the library, had more of
a performance hit; I prefer to leave my imagination out of
profiling :). Certainly I agree with you on principle
though.

In practice I don't see the point in altering the built-in
function since I've never needed to use forEach() in that
way. If I did have to, I'd wrap the specific forEach() in
try/catch by hand rather than make changes to the semantics
of the original. Either that or I'd make sure the contents
of the array is 'valid' in such a way that the forEach()
loop wouldn't need to be aborted.
`return false` idiom probably originates from intrinsic
event handlers, where it would prevent event's default
action. IIRC, jQuery uses it in its own `each`
implementation. `return false` could also be easier to
use, as you don't need to remember the name of error
object.

True. I guess I was talking more about reading someone
else's code rather than writing it. In our team I'm fairly
certain that using 'return false;' would cause more problems
of understanding than throwing an exception. --Antony
 
J

Jorge

I don't believe there is a reason to. Alter your code.

The reason is that what I'm doing may throw a runtime exception.
myArray.filter(...).forEach(...);

The reason is not in the input. It's not something that I can know
beforehand if it's going to throw.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top