Compatibility
Published
The iterator helpers are the newest way to write a pipeline in JavaScript: map, filter, take, drop, flatMap and the rest, called directly on an iterator instead of on an array. The entire reason to use them is that they are lazy, which makes them the natural thing to reach for over a generator, a cursor or any source too large or too infinite to materialise. That laziness is also the property most likely to be quietly lost in a build step, so it is the property this measurement was built around.
What was measured
The sample builds twelve results. A take off an infinite generator. A filter into a map into a take, again off an infinite source. A chain with a counter inside the generator, so the number of values actually pulled is observable rather than assumed. Then drop, flatMap, reduce, some, every and find. Then a helper chain over a real Map, using both keys and values. Finally a chain over a hand-written iterable, adapted with Iterator.from.
Two of those exist specifically to make a vacuous pass impossible. The infinite sources cannot produce a result at all unless laziness is preserved, so a chain that eagerly evaluated would fail rather than quietly agreeing. The pull counter turns laziness from something you assert into something you count: the unprotected run pulled exactly three values to produce three results, and any change to that number would show.
Protected in five configurations -- the default target, the modern target, both gate profiles and the string-transform profile -- every one of the twelve results was identical, and the pull count stayed at three in all five. Laziness survived intact, including on the default output target.
That last detail is worth stating plainly, because the companion article on iteration found the opposite result for for-of on the same target. There is no contradiction. A helper chain is a sequence of ordinary method calls on an object; there is no syntax for the output target to lower, so nothing is rewritten. The for-of defect is a property of the loop syntax, not of lazy iteration in general.
Why the chain is untouched
Every part of a helper chain is a method call with a dot and a name. The protection passes rename identifiers, move and encode string literals, and lower syntax that the selected output target cannot express. A chain of method calls presents nothing to any of those except names, and the names here belong to the standard library rather than to the file.
The laziness itself lives entirely inside the runtime. When you call take(5) on an iterator, the engine returns a new iterator object that holds a reference to the source and a counter; nothing about that arrangement is expressed in your file, so nothing in your file can be transformed in a way that changes it. The protected build gets the same objects from the same engine.
The string transforms are equally uninvolved. There are no meaningful string literals in a helper chain, and where the sample does use them -- the Map keys -- they round-tripped identically, which is the same result the string transforms produce everywhere else on this site: they change where a literal is stored and how it is encoded, not what it decodes to.
So the base result here is genuinely boring, and it is worth having measured precisely because the surrounding area is not. Knowing that helper chains are clean on both targets is what makes the for-of result actionable: rewriting a lowered loop as a helper chain is a real workaround rather than a guess.
Renaming the helper names fails immediately
A member pattern matching map, filter, take, drop and toArray produced no output at all. The first line threw TypeError: naturals(...)._0x2 is not a function. The generator was called, an iterator came back, and the method that should have been take was a generated name that does not exist on it.
This is the ordinary over-broad-pattern failure and it is the good kind. It happens on the first call, it names the receiver, and the generated name in the message is an unmistakable signal that renaming is the cause rather than anything about your logic. A pattern matching reduce, some, every, find and flatMap behaved the same way, as did one matching keys, values and forEach.
The names are the hazard rather than the helpers. map, filter, find, keys, values, some and every are all extremely plausible field names on an application object, which is exactly how they end up inside a member pattern that was written with something else in mind. The mitigation is the one this site recommends throughout: anchor member patterns to names distinctive to your own code.
Nothing here is specific to the new helpers. The identical failure has been measured on this site for array methods, string methods and Math functions. It is included because a reader arriving at this page with a broken chain needs to be able to rule it in or out in one step, and the generated name in the error message is that step.
The sharp boundary: whose iterator is it
The most useful result of this measurement is a pair of arms that differ in one respect. A member pattern matching next, value and done -- the iteration protocol itself -- left eleven of the twelve results completely correct. Every helper chain over a generator, an array iterator and a Map produced exactly the right answer. The twelfth failed with TypeError: undefined is not a function, and the twelfth is the one whose source is a hand-written iterable adapted through Iterator.from.
The reason is that a built-in iterator implements the protocol inside the engine, where your member pattern cannot reach. When you chain helpers off a generator or off map.keys(), the next being called is the engine's, and it is not a name in your file. Renaming a property called next in your source changes nothing about it.
A hand-written iterable is the opposite. Its next, its value and its done are properties of an object literal in your file, and they are the ones the engine reads. Rename them and the adapter is handed an object that does not implement the protocol it claims to.
That gives a rule that is easy to apply and does not require knowing anything about the transforms: if the iterator came from the language, the protocol names are not yours and renaming cannot touch them; if you wrote the object, they are yours and renaming will. The same distinction decides the outcome for a set-like object handed to a Set operation, and for the underlying-source callbacks of a stream.
Where the helpers are actually worth reaching for
Because the chains measured clean on the default output target and for-of over a lazy source did not, a helper chain is a genuine workaround for the loop rewrite rather than a stylistic preference. A loop that breaks after a fixed number of values becomes a take followed by toArray, and the measured pull count confirms that the protected build pulls only what it needs.
The same applies to the early-exit searches. find and some stop at the first match by specification, and both were measured stopping correctly over an infinite source after protection. Rewriting a for-of loop with a conditional break into a find call changes a construct the default target rewrites into one it leaves alone.
There is a caveat to state honestly. The helpers are a recent addition to the language, so the reason a build might be on the older output target -- support for an old runtime -- is often the same reason the helpers are not available there. If that describes your deployment, the workaround for the loop rewrite is an explicit while loop driving next by hand, which is supported everywhere and which the transform also leaves alone.
Check availability rather than assuming it. The helpers are methods on iterator prototypes and a missing one is a plain TypeError at the first call, which is indistinguishable at a glance from the renaming failure described above. The generated name in the message is what separates them: _0x2 is not a function is renaming, take is not a function is a runtime that does not have it.
How to check your own build
The single most informative check is a counter. Put one inside a generator, run your real chain over it, and compare the count across an unprotected and a protected build. Laziness is the property worth verifying and it is the only one that a comparison of results will not reveal, because an eager chain and a lazy chain produce identical output on any finite source.
If a chain is throwing, read the receiver in the error message before anything else. A generated name on either side of the dot means a member pattern reached the call, and the fix is to narrow the pattern rather than to change the chain. A real method name means the runtime does not implement that helper.
If a chain is producing wrong values rather than throwing, the helpers are unlikely to be the cause and the source is the place to look -- particularly if the source is an object you wrote. Verify that its protocol names survived by logging the result of one manual next call and checking that the object coming back still has properties called value and done rather than generated names.
Finally, keep the two questions separate when testing. Whether a chain produces the right values and whether it produces them lazily are different properties with different failure modes, and only the second one is affected by the surrounding build settings in the way this series keeps finding.
Frequently asked questions
Do iterator helpers still work after obfuscation?
Yes. A chain of map, filter, take, drop, flatMap, reduce, some, every and find was measured producing identical results in five protection configurations, including both output targets and the string-transform profile. A helper chain is a sequence of ordinary method calls, so there is no syntax for an output target to lower and nothing for the transforms to rewrite.
Is laziness preserved after protection?
Yes, and it was measured rather than assumed. A chain over an infinite generator with a counter inside it pulled exactly three values to produce three results, both before and after protection, in all five configurations. The infinite source is what makes that result meaningful: a chain that had become eager could not have produced any output at all.
Why does my helper chain throw a TypeError after protection?
Almost certainly because a member pattern matched a helper name. Renaming map, filter, take, drop or toArray was measured throwing on the first call, with a message naming the receiver and a generated name such as _0x2. Those names are also plausible application field names, which is how they end up in a pattern written for something else. Narrow the pattern to names distinctive to your own code.
Does renaming next, value and done break a helper chain?
Only when the source is an iterable you wrote. With that pattern applied, every chain over a generator, an array iterator and a Map produced correct results, because those iterators implement the protocol inside the engine where a member pattern cannot reach. The one chain that failed was over a hand-written iterable adapted with Iterator.from, whose protocol names are properties in your own file.
Should I rewrite for-of loops as helper chains before protecting?
It is a reasonable workaround if you are on the default output target and the loop runs over a lazy source. That target rewrites such a loop in a way that drains the source first, while helper chains measured clean on the same target. A loop that breaks after a fixed count becomes take followed by toArray. Switching to the modern output target fixes the loop directly and is the smaller change.
How do I tell a renaming failure from a missing helper?
Read the name in the error. A message like _0x2 is not a function means a member pattern reached the call, because that name was generated by the build. A message naming a real method, such as take is not a function, means the runtime does not implement that helper -- they are a recent addition, and an older runtime is often the same reason a build is on the older output target.
What should I test on a protected build that uses helper chains?
Count rather than compare. Put a counter in the producer and assert on how many values were pulled, because an eager chain and a lazy chain return identical results over any finite source and a comparison of outputs cannot distinguish them. Then check any source object you wrote yourself, since those are the only ones whose protocol names a member pattern can reach.
Related reading