Although there is a prefer_foreach
lint, that recommendation is specifically for cases where you can use it with a tear-off (a reference to an existing function). Effective Dart recommends against using Iterable.forEach
with anything else, and there is a corresponding avoid_function_literals_in_foreach_calls
lint to enforce it.
Except for those simple cases where the callback is a tear-off, Iterable.forEach
is not any simpler than using a basic and more general for
loop. There are more pitfalls using Iterable.forEach
, and this is one of them.
-
Iterable.forEach
is a function that takes a callback as an argument.Iterable.forEach
is not a control structure, and the callback is an ordinary function. You therefore cannot usebreak
to stop iterating early or usecontinue
to skip to the next iteration. -
A
return
statement in the callback returns from the callback, and the return value is ignored. The caller ofIterable.forEach
will never receive the returned value and will never have an opportunity to propagate it. For example, in:bool f(List<int> list) { for (var i in list) { if (i == 42) { return true; } } return false; }
the
return true
statement returns from the functionf
and stops iteration. In contrast, withforEach
:bool g(List<int> list) { list.forEach((i) { if (i == 42) { return true; } }); return false; }
the
return true
statement returns from only the callback. The functiong
will not return until it completes all iterations and reaches thereturn false
statement at the end. This perhaps is clearer as:bool callback(int i) { if (i == 42) { return true; } } bool g(List<int> list) { list.forEach(callback); return false; }
which makes it more obvious that:
- There is no way for
callback
to causeg
to returntrue
. callback
does not return a value along all paths.
(That’s the problem you encountered.)
- There is no way for
-
Iterable.forEach
must not be used with asynchronous callbacks. Because any value returned by the callback is ignored, asynchronous callbacks can never be waited upon.
I should also point out that if you enable Dart’s new null-safety features, which enable stricter type-checking, your forEach
code will generate an error because it returns a value in a callback that is expected to have a void
return value.
A notable case where Iterable.forEach
can be simpler than a regular for
loop is if the object you’re iterating over might be null
:
List<int>? nullableList;
nullableList?.forEach((e) => ...);
whereas a regular for
loop would require an additional if
check or doing:
List<int>? nullableList;
for (var e in nullableList ?? []) {
...
}
(In JavaScript, for
–in
has unintuitive pitfalls, so Array.forEach
often is recommended instead. Perhaps that’s why a lot of people seem to be conditioned to use a .forEach
method over a built-in language construct. However, Dart does not share those pitfalls with JavaScript.)