Can the team make `continue` and `break` keywords work in built-in array functions like forEach()?

Hello,

I was thinking, that the continue and break keywords are working only in a simple for loop.
I was trying to insert them in ternary operator or the powerful forEach() function, but it gives me the error of not in a loop or someting like that.

So, I was wondering, can the team make these work in ternary operator statements and built-in array functions, if not using them in generic for loops?

So, if I want to reduce the code I write, I require keywords to function as normal, everywhere, right?
Or am I blatantly idealizing things?

Wonder with me and give me a response, please.

Cheers

For this specific case, you might as well just use a for of loop rather than .forEach, i.e.:

for (const item of someArray) {
    // Whatever conditions you want here
    if (someCondition(item)) {
        break;
    } else if (otherCondition(item)) {
        continue;
    }
}

I require keywords to function as normal, everywhere, right?

Well break terminates the nearest SYNTACTIC loop, this is perfectly consistent, it doesn't do different things in different places.

The thing is .forEach is NOT a loop, it's a method call that internally happens to contain a loop. The reason you can't break the internal loop is because ultimately you're not supposed to know how a method is implemented, it's the same reason you can't access variables local to another function.

Or am I blatantly idealizing things?

Non-local control flow is a thing in languages like Kotlin, however Javascript does not work like Kotlin and adding such a feature would be immensely difficult due to Javascript's dynamic nature. Languages like Kotlin can support it because they are compiled ahead of time and have things like inline functions.

Like to give you an idea having array.forEach support any notion of "inline function" is essentially impossible because array.forEach itself is a dynamic lookup on the array object to even get the method, like I can freely replace .forEach with another function:

// Perhaps I replace it with a faster version, or a version that prints when it's called
// or maybe I just destroy the whole function, all of these are possible things to do
Array.prototype.forEach = function() {
    // ...
}

Really there is only one possible implementation of non-local control flow, and that would be exception-based break/continue, however members of TC39 have previously objected to such behaviour for various reasons and it's probably unlikely such a proposal would get through unless there was very large demand and the various problems were worked out.

2 Likes

Thank you for your thourough answer. No I understand it why can't I use these keywords outside of a loop.

There is technically this block params proposal, which would allow programmers to receive a block as a parameter, that could even contain continue/break, and then the function receiving the block would be allowed to handle all of this. With a proposal such as this, the forEach function could be modified to work like this:

myArray.forEach {
  if (something) {
    continue;
  } else {
    break;
  }
}

Unfortunately, this proposal has been pretty stagnant for a while, so I'm not sure if it's going to go anywhere.

Also, as @Jamesernator mentioned, we would basically just be making forEach behave more like a for-of loop. Might as well just use for-of instead. TBH, I never really use forEach anymore, as for-of is strictly more powerful in every way. The one exception is that forEach allows you to easily pass in a function defined elsewhere, like array.forEach(someFn), but in practice, I've never needed this.


As for break/continue in a ternary.

Break/continue, by nature, is a statement, not an expression. You're instructing the language on how to perform some control flow. It has no business being in an expression position. Ternary fits best when you need to evaluate and receive two potentially different values, depending on a condition, which isn't what's happening here. A normal if/then works better when you're trying to execute an imperative sequence of steps. This means, if you need to conditionally use break/continue, then use break/continue with a normal if, not with ternary, it's the better fit.

That being said, the do expression proposal does technically enable this behavior, since it enables any statement to be used in the expression position. The fact that break/continue could be used as an expression was seen more as an unfortunate necessary consequence to keep the proposal consistent, rather than something people should actually use. But, it is possible to do it like this:

const x = condition ? 2 : do { break; };

Though, it makes more sense to just write it like this:

if (!condition) {
  break;
}

const x = 2;
3 Likes

I did not know about this use case possibility. Will try it out.

Suggestion revoked in correspondence to reply.


Original response You can already "continue" in a forEach with the `return` keyword:
array.forEach((e) => {
	if(wantToContinue()){
		return; // this will continue
	}
})

If want want to have a "break" functionality, what you could do is have a custom method with an extra callback parameter, like this:

Array.prototype.forEachWithBreak = function(callback, thisArg){
	thisArg ??= globalThis;
	const brk = Symbol();
	for(let i = 0; i < this.length; i++){
		const response = callback.call(thisArg, this[i], i, this, brk);

		if(response === brk) break;
	}
};

Now, this works like so:

array.forEachWithBreak((e, i, arr, brk) => {
	if(wantToContinue()){
		return; // this will continue
	} else if(wantToBreak()){
		return brk; // this will break
	}
})

This may suit your needs.

Or you can use some()

array.some(() => {
  if (condition) return 1 // break
})

Please do not modify objects you don't own - especially builtins - and please don't suggest doing so. Maintainable JavaScript: Don’t modify objects you don’t own - Human Who Codes is helpful reading.

1 Like

continue; abd break; in Array.prototype.forEach() would be helpful.

continue is return. for break, though, it'd be simpler to use some or every and return the appropriate boolean.

A very late thought... Couldn't

array.foreach(() => {
   if (condition) {
      doSomething();
   }
   else {
      break;
   }
});

...be translated into something like

Symbol.break = Symbol("break");
try {
   array.foreach(() => {
      if (condition) {
         doSomething();
      }
      else {
         throw Symbol.break;
      }
   });
}
catch(e) {
   if (e != Symbol.break) {
      throw e;
   }
}

...?

I'm not really advocating for something like this though. I think if you want to be able to break the loop, then just don't use foreach(). While I can appreciate that there are times when that's not an option do to library code. However, I'm also of the mindset that if you're using library code that uses foreach or other similar helpers, then it's better to pre-filter the array you're feeding it since you know it's going to act against everything you give it.

Guess another point against it - throwing a special "break" value still requires explicit library support - they'd have to have something in their forEach implementation to catch and handle it properly, otherwise things won't go very well.