/ coffee-script

Gotchas: Loops in CoffeeScript are not closures

So I’ve decided I’m going to start writing about the various gotchas that I come across in this software engineering journey of mine. My hope is that these writings will get a decent page index and will bubble to the top of the search results the next time someone runs into the same issues I do. In today’s edition of “things you don’t expect languages to do” we’re going to have a brief chat about CoffeeScript’s for loops, and some gnarly behavior to be aware of if you aren’t currently.

So, I started working with CoffeeScript around version 1.3.1. In this version, the contents of a for expression in CoffeeScript were wrapped in a JavaScript IIFE, or Immediately-Invoked Function Expression. In JavaScript, those are the things that look like this:

(function() {
)();

In version 1.3.2, CoffeeScript removed the IIFE from their for loops. So, the end result of that change was that if you wrote the following loop in CoffeeScript:

things = ["a", "b", "c"]
for thing in things
  setTimeout () ->
    console.log thing
  , 10

You might expect to see the following output on your console:

> a
> b
> c

You would, in fact see:

> c
> c
> c

The reason is that by the time the console.log is evaluated, the value of thing is set to “c” because the actual console statement only executes after the loop has completed and “c” is the value thing had at the end of the loop. Head hurting yet?

If you follow along on this blog, I’m sure you’ve seen my mention of issues with my project that imports data from the Georgia General Assembly. Specifically imports were broken in strange ways. It turns out that for loops behaving in this manner were the entire reason for those issues. I didn’t actually figure out what was going on until we ran into the issue at Elemica earlier this week, which resulted in me posting a bug on the coffee-script project. The net-net is that this is intended behavior.

As I noted in the GitHub ticket linked to above, this is particularly gnarly behavior when combined with jQuery, or really any other library that behaves in an asynchronous fashion. So, how do you get around this issue? do.

things = ["a", "b", "c"]
for thing in things
  do (thing) ->
    setTimeout () ->
      console.log thing
    , 10

This will essentially cause thing to be frozen, as you would have liked it have been in the first place. There’s a proposal underway to consolidate this into one line in a future version of CoffeeScript to make it even cleaner.

Anyway, it’s a Friday night and I just kind of spun this out as a quick “heads up” to all my fellow CoffeeScripters out there. If there are any errors, omissions, or unintended easter eggs in this post please let me know in the comments and I’ll correct them. Hopefully you won’t spend quite as much time as I did scratching you head over variables in for loops changing or behaving unexpectedly.