
Transpiled For-Of Loops Are Bad for the Client - based2
http://daverupert.com/2017/10/for-of-loops-are-bad/
======
underwater
The author has huge gaps in his understanding the language works, and these
tools work, and he seems to have zero interest in correcting that. It sucks
that he had a bad experience. And, yes, it's a lot to understand. But he
blames Babel, the authors of polyfill code, and Twitter without putting any
attempt into filling the gaps in his understanding.

The Babel homepage literally says what it does.

> These plugins allow you to use new syntax, right now without waiting for
> browser support.

When a Twitter user tried to help him by asking how a syntax transform could
polyfill `forEach` on a `NodeList` -- something that would be impossible in JS
-- he was completely dismissive.

> Science? ‍️ I dunno. Maybe if the object was previously queryselected? [...]
> I'm way past that problem now.

Polyfills can be heavy because they have to be forward compatible and
implement behavior to spec. If they don't then you end up shipping code that
breaks when browsers are updates and the polyfill becomes redundant. That's
how you break the web.

------
bluepnume
You can set babel to `loose` mode to avoid having the transpiled output use
`Symbol`. What I would love though is a "super-loose" mode which takes for-of
and transpiles it directly to for-in -- i.e. excluding any support for
iterators and just functioning on indexable collections with a defined
`.length`. That would be enough to get concise output in most cases if you're
confident you're always iterating over static collections.

~~~
hzoo
I answered this on twitter:
[https://twitter.com/left_pad/status/936721918840983554](https://twitter.com/left_pad/status/936721918840983554)

And added this option + optimization for v7
[https://github.com/babel/babel/tree/master/packages/babel-
pl...](https://github.com/babel/babel/tree/master/packages/babel-plugin-
transform-for-of#assumearray) so can look forward to that now =D.

------
DiThi
For loops are the single biggest reason we still use Coffee Script. They're
the most efficient and most compatible of the alternatives. The syntax of the
loops is very clean and they all compile to simple "for( ; ; )" or "for( in )"
loops.

And a few months ago that we considered moving to something else, Coffee
Script 2 was released with all that was missing for us.

------
jasonthevillain
I've learned the hard way to check Babel's output for sanity.

Sometimes its fine.

Other times it's madness or pulls in a massive polyfill. That's sort of
inherent to making a transpiler: they need to match the spec. I usually just
need one specific behavior that could be written in a 3 line helper function.

~~~
Klathmon
They have a "loose" mode for most compilations that can benefit from them.

In loose mode they might not follow the letter of the spec, but can cheat for
speed or size.

------
philmander
One upon a time web developers had to wait for browsers to support new
features. Using Babel does not automatically mean that this no longer true.

~~~
larzang
The problem is that IE11 is not eol until the end of 2020, and management can
be loathe to drop support for it while it still makes a blip in the traffic,
even a tiny one.

It's one thing to wait for the actually supported browsers to all catch up on
a new feature, but IE11 is abandonware that can't be abandoned, and the
situation just keeps getting worse.

------
jrochkind1
Sometimes I think that it might actually be fine to just stick to JQuery and
ES5 after all.

------
TazeTSchnitzel
Why would a polyfill of Symbol make this work? If a browser doesn't support
Symbol, it won't have a property named [Symbol.iterator] on DOMNodeList,
right?

~~~
lexicality
Because the 259kb polyfill also adds iterator support to everything.

------
fiddlerwoaroof
The other way to deal with this particular problem is to do:

    
    
        [].forEach.call(myNodeList, myCallback)

~~~
vkjv
Good tip, but while it works in this specific case, it isn't generally
equivalent. "for of" works on anything that is iterable. This will only work
on array-like objects.

~~~
Klathmon
And the major reason I use for-of is because you can `await` within a loop
easily.

~~~
Maarten88
Awaiting in a loop usually is not a good idea. Map with Promise.all / any is
probably much faster.

edit: now reading about for..await..of. Interesting, I never used that.

~~~
Klathmon
Even outside of for..await..of you still need to do async things inside a
synchronous-like loop.

------
hajile
The transpile issue on mobile is greatly overstated. The vast majority of
mobile browsers (by market share) have very good es6 support. Instead of one
version, websites need one with minimal transpiling and one with full
translation for older browsers.

Sure you need a little more setup, but it's not only better for clients, but
it's also much better for development because your source maps, breakpoints,
and actual variables are much closer to what the browser is actually executing
(this is especially important for generators because they compile to case
statements that aren't easy to debug).

------
carussell
Go one step further, eliminate the polyfill, and just write a traditional for
loop.

Several years ago, I decided to try out the then-newish E4X for-each syntax.
It was only implemented in SpiderMonkey, but that was okay because not only am
I not a web developer, this project wasn't meant to run on the web, either—it
just happened to be in JS. I knew for a fact that this code wouldn't be
running anywhere except SpiderMonkey, so I dug in. After some research,
though, I discovered some problem. Some subtlety that made it a non-starter.
So I kept writing traditional for loops.

A couple years later, I was starting work on a kind-of-new codebase; it was
essentially greenfield, but I knew I was going to end up cribbing a bunch from
an existing codebase. When my fingers began typing out the familiar `for (`, I
paused. This would be the first for loop introduced in the project, and I
should maybe make a decision to get things right from the beginning. By now,
for-of had arrived, and it was being standardized in ES _x_ proper. Should I
maybe do another survey of the lands and look into giving the new iteration
syntax a shot? It was a simple iteration: a straight pass through a list of
items, one after another. And in addition to being able to address items by
index, they all had the equivalent of a "next" pointer for the item that
followed. So I deferred; I went with a while loop instead, chasing "next"
pointers to avoid having to do some in-depth research on the then-current
state of `for`. I got pretty far doing this, actually, and was able to go the
next few days writing all my loops in this way, just to avoid tacitly
endorsing either for-of or traditional for loops.

After several days, however, that ran out, and I came to point where I would
not be able to sidestep it and needed to make a decision. So I stopped, and
dug in again. Another problem surfaced: for-of was only implemented in the
newest releases of SpiderMonkey, and the only enhanced iteration syntax
available in previous releases that this code would need to run on was the
now-deprecated for-each. I thought really hard about this. Was there some part
of the language that could be exploited to find some clever way around this?
No. Maybe consider a transpiler? After all, for-of would be standard, so
_eventually_ support is going to be widespread enough that even the lowest
common denominator will have native support...

But then I stopped. Because the whole thing is stupid. Just write a damn for
loop. What was the real problem? Trying to save time? Except how much time was
already sunk into trying to shave off, what? Like _one second_ of typing?
Maybe two? And the tradeoff for that development time gain is (a) a runtime
performance penalty; (b) having to either throw everything older than version
_x_ by the wayside or spend all those extra seconds and more to put some
transpiler crud in place; and (c) code that's harder to understand for anyone
who doesn't fetishize staying on top of the newest language features. The
latter also involves people who typically work in other languages and would
probably immediately understand `i = 0; i < n; i++` (even if it's not
idiomatic for their preferred ecosystem), but likely wouldn't immediately
grasp the nuances of the new iteration protocol.

What's wrong with `i = 0; i < n; i++`, anyway? It's not even that verbose.
What it is, is predictable. Which means your eyes get used to it, just like
any other pattern. Except traditional for loops are powerful, too, because you
might want to do something, say, for every fourth item. Or maybe skip the
first. Or iterate backwards. And you can. In each case, anything not fitting
the traditional pattern will stick out.

Anyway, I still write traditional for loops today. I don't feel like I'm
missing out on anything, and my thoughts very much echo the sentiments in an
essay published a few years ago on a similar topic: "Java for Everything".

[https://www.teamten.com/lawrence/writings/java-for-
everythin...](https://www.teamten.com/lawrence/writings/java-for-
everything.html)

~~~
torgard
One point towards using Array prototype functions (map, filter, reduce, etc.),
is that they are often much more readable.

For loops are harder to skim.

Still, I don't understand the for loop hate. I have seen people argue against
'pointless optimizations', in the name of readability. And while yeah,
arr.filter().map() is probably faster to comprehend, _you 're still iterating
through the array twice_. Pointless optimizations, not so much when the array
is thousands of elements long.

~~~
yxhuvud
Iterating a long array twice to do two simple things can often be faster than
iterating once to do a more complicated thing. See for example
[https://stackoverflow.com/questions/8547778/why-are-
elementw...](https://stackoverflow.com/questions/8547778/why-are-elementwise-
additions-much-faster-in-separate-loops-than-in-a-combined-l) .

The wonders of modern processors and compilers.

~~~
taeric
That link is more about the pitfalls of page alignment. Caches being a very
dangerous topic.

Getting the data aligned in a cache friendly way returns those loops to the
expected behavior of one iteration being faster than two.

------
kozak
I also noticed that Babel's transpiled ES6 classes suffer from abstraction
leaks quite a lot. This is especially bad if you have two classes in the same
class hierarchy, where one class resides in a babelified module, and another
one in a native module.

------
javan
See also:
[https://twitter.com/javan/status/907949203325964288](https://twitter.com/javan/status/907949203325964288)

------
zengid
It looks like Typescript also transpiles to a similar try..catch..finally
pattern:

[https://blog.mariusschulz.com/2017/06/30/typescript-2-3-down...](https://blog.mariusschulz.com/2017/06/30/typescript-2-3-downlevel-
iteration-for-es3-es5#the-downleveliteration-flag)

~~~
ChrisSD
Are you sure?
[https://www.typescriptlang.org/play/#src=const%20inputs%20%3...](https://www.typescriptlang.org/play/#src=const%20inputs%20%3D%20document.querySelectorAll\('input%2C%20select%2C%20textarea'\)%3B%0D%0Afor%20\(let%20input%20of%20inputs\)%20%7B%0D%0A%20%20%20%20console.log\(input\)%3B%0D%0A%7D%0D%0A)

~~~
zengid
..with the `--downlevelIteration` flag set and ES5 or ES3 set as the
compilation target. I should have clarified.

From the article:

"TypeScript 2.3 introduced a new `--downlevelIteration` flag that adds full
support for the ES2015 iteration protocol for ES3 and ES5 targets.
`for...of`-loops can now be downlevel-compiled with correct semantics."

------
alangpierce
> First stop, I looked at babel-polyfill.js but was surprised to see it is
> 259kb unminified/uncompressed. I’m sure it does a lot, but the entire
> JavaScript bundle for the whole site is only 93kb unminified/uncompressed.
> It’s hard for me to recommed a +300% increase.

I just tested it, and the minified and compressed babel-polyfill is 34K. Going
from 12K of JS to 46K of JS just to make modern loops work may be a little
unsettling, but those are both really small numbers these days. The blog post
is about 10x bigger. It's also worth noting that babel-polyfill adds lots of
other useful stuff; there are plenty of similar problems that you'll never
have to think about if you're willing to drop in the whole babel-polyfill.

I think that for most use cases, bundle size is a distraction and it's best to
just focus on building things. You should still keep an eye on bundle size in
case it gets out of hand, but I don't think that's the case here.

~~~
always_good
I agreed with you until I had to be wary of performance on mobile devices
where the user may have to pay 10s of milliseconds just on boot per every 15kb
of javascript you add.

At the end of the day you ask yourself "do I really need to add 34kb for
'for..of' or can I get away with a small concession?" like the blog post
asked. You may find that it might not cost you much to pay attention to your
trade-offs.

------
msiggy
You know its a good language when you have this much trouble writing a simple
for loop.

~~~
always_good
You're confusing the myriad of client runtime environments with the language.

Whining about the former is to just whine about history, like how 'referer' is
misspelled.

------
cromwellian
What is iterator.return()? That seems non-standard?

------
35bge57dtjku
> but I sometimes wonder if we’re living in a collective delusion that the
> current toolchain is great when it’s really just morbidly complex.

Epiphany of the year there.

~~~
TAForObvReasons
> I’m not sure this is an issue with Babel per-se, but maybe a more meta topic
> of the cost of polyfilling.

The overarching problem is that the web platform itself is so fragmented, with
different levels of support for various features, that polyfilling and
transpilation are important. Strictly speaking, if every relevant browser
supported for..of natively, there should be no need to transpile.

You could avoid the whole for..of problem by writing ES5 JS code, but for many
people that's apparently a worse option than dealing with Babel.

~~~
inferiorhuman
> You could avoid the whole for..of problem by writing ES5 JS code, but for
> many people that's apparently a worse option than dealing with Babel.

The big reason I'm so excited about web assembly is that now we can escape the
albatross of JS/ES. I've started playing with rust targeted to emscripten and
it's such a breath of fresh air (both the language and toolchain).

~~~
kbenson
I was just thinking the same thing while reading this article but, upon
thinking about it, you'll likely have to ship the stdlib of whatever language
you're targeting as well as your code (although I'm sure some interesting
omissions of the stdlib can be automated determined based on the actual use).
That's traditionally why Rust binaries are large (they statically compile in
their standard library).

Statically compiling a simple hello world C program results in a 700+ KB
executable. By researching And adding various options to gcc to reduce size, I
was able to get the binary down to just under 650 KB (277 KB gzipped), so
_progress_! :/

There are undoubtedly smaller libc implementations[1], but I think I've
illustrated the problem adequately. Having an entire runtime in the browser
gives JS a fairly large advantage as I see it, at least currently.

1:
[https://en.wikipedia.org/wiki/Dietlibc](https://en.wikipedia.org/wiki/Dietlibc)

~~~
cwzwarich
Rust binaries are also large because they copy some of C++'s bad decisions:

1) Generics are instantiated and compiled separately with types for which the
generics have identical behavior.

2) Functional abstraction is encouraged (not a bad thing!) and inlining
heuristics are overly aggressive to compensate.

3) By default, almost every function call has to deal with the possibility
that the callee might throw an exception. This is smaller than 1) and 2), but
it still matters.

~~~
inferiorhuman
Except that rust doesn't have exceptions.

~~~
cwzwarich
Rust actually does have exceptions. Rust unwinding is implemented using
LLVM/Dwarf exceptions, and can be caught using std::panic::catch_unwind:

[https://doc.rust-lang.org/std/panic/fn.catch_unwind.html](https://doc.rust-
lang.org/std/panic/fn.catch_unwind.html)

Idiomatic Rust doesn't use them for error handling, and they can be disabled
entirely with a command-line option, but the implementation still has to deal
with all of the issues that come with having exceptions.

~~~
inferiorhuman
Well, damn.

------
based2
[https://lobste.rs/s/wwgnlh/transpiled_for_loops_are_bad_for_...](https://lobste.rs/s/wwgnlh/transpiled_for_loops_are_bad_for_client)

------
loftsy
Try out dart or typescript.

~~~
spraak
But those just transpile to JS as well and likely suffer the same problems.

------
ryanmarsh
I get it. It’s roughly 5 times the lines of code and there are better
polyfills for IE (sounds like an easy bug to fix in Babel) but what’s the
actual performance impact _when it runs_?

