How to correctly use functions inside loops in CoffeeScript 2?

70 views Asked by At

1. Summary

I can’t find, what is the correct way to use functions inside loops (or what to replace it with) in CoffeeScript 2 in 2024.

Should I use one of the variations I’ve given below, or CoffeeScript has a better solution?

2. MCVE

I need to run a function inside a loop. The global function setTimeout() in my MCVE solely for example to make the MCVE simple. In real projects I use other functions.

Example array:

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"]

I want to get this console output:

Kira is Goddess!
Kira is Ideal!
Kira is Perfection!

3. Not helped

3.1. “for…of” loop

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"]

for kiraСharacteristic from kiraСharacteristicsArray

    setTimeout (->
        console.log "Kira is #{kiraСharacteristic}!"
    ), 0

Compiled JavaScript:

var kiraСharacteristicsArray, kiraСharacteristic;

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"];

for (kiraСharacteristic of kiraСharacteristicsArray) {
  setTimeout((function() {
    return console.log(`Kira is ${kiraСharacteristic}!`);
  }), 0);
}

Result:

Kira is Perfection!
Kira is Perfection!
Kira is Perfection!

Simple ECMAScript solution doesn’t work in CoffeeScript:

  1. CoffeeScript haven’t const and let.
  2. Unfortunately, CoffeeScript compiles solely to var, not to const and let.
  3. I haven’t found an ESLint plugin or any other tool that would correctly replace var with const and let in JavaScript files.

3.2. Closure

As @Alex Wayne described in his answer:

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"]

for kiraСharacteristic from kiraСharacteristicsArray

    do(kiraСharacteristic) ->

        setTimeout (->
            console.log "Kira is #{kiraСharacteristic}!"
        ), 0

Compiled JavaScript:

var kiraСharacteristicsArray, kiraСharacteristic;

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"];

for (kiraСharacteristic of kiraСharacteristicsArray) {
  (function(kiraСharacteristic) {
    return setTimeout((function() {
      return console.log(`Kira is ${kiraСharacteristic}!`);
    }), 0);
  })(kiraСharacteristic);
}

I get expected console output, but ESLint return no-shadow error:

6:13 error 'kiraСharacteristic' is already declared in the upper scope on line 1 column 16 no-shadow

3.3. “forEach()” method

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"]

kiraСharacteristicsArray.forEach (kiraСharacteristic) ->

    setTimeout (->
        console.log "Kira #{kiraСharacteristic}!"
    ), 0
    

Compiled JavaScript:

var kiraСharacteristicsArray;

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"];

kiraСharacteristicsArray.forEach(function(kiraСharacteristic) {
  return setTimeout((function() {
    return console.log(`Kira ${kiraСharacteristic}!`);
  }), 0);
});

I get expected console output, but Unicorn return no-array-for-each error:

5:11 error Use for…of instead of .forEach(…) unicorn/no-array-for-each

3.4. “continue” statement

I fixed JavaScript above use ESLint, Unicorn and prefer-arrow-function plugin use eslint --fix CLI command:

var kiraСharacteristicsArray;

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"];

for (const kiraСharacteristic of kiraСharacteristicsArray) {
   setTimeout((() => console.log(`Kira ${kiraСharacteristic}!`)), 0); continue;
}

I get expected console output, but ESLint return no-continue error:

6:64 error Unexpected use of continue statement no-continue

4. Don’t offer

Please, don’t offer:

  1. “Use JavaScirpt, CoffeeScript is bad”.

  2. “Don’t use functions inside loops. Read JSHint’s warning”. Yes, JSHint return warning W083, when I use a function inside loop:

    jshint: warning W083 - Functions declared within loops referencing an outer scoped variable may lead to confusing semantics. (console)

    But it seems to me that loopfunc JSHint’s option is obsolete and doesn’t take into account the ECMAScript features const and let.

  3. “Use Civet instead of CoffeeScript” Yes, I know that Civet supports const and let, see my MCVE written on Civet. But my question about CoffeeScript.

2

There are 2 answers

0
Daniel X Moore On
kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"]

`for(const kiraСharacteristic of kiraСharacteristicsArray)
    setTimeout(() =>
        console.log("Kira is " + kiraСharacteristic)
    ), 0)
`
0
Amadan On

Your main problem is kiraCharacteristic being closed over (as you are doubtless aware). If you don't let it get into the closure, you're fine. For clarity, it is not "using functions in a loop" that is a problem; it is creating functions in a loop using function literals, because that is what makes a closure.

setTimeout is able to pass arguments to its callback; since the arguments passed this way are outside of the callback, they are not closed over.

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"]

for kiraСharacteristic from kiraСharacteristicsArray

    setTimeout ((innerKiraСharacteristic) ->
        console.log "Kira is #{innerKiraСharacteristic}!"
    ), 0, kiraСharacteristic

If you are not using setTimeout, you should make a MCVE that corresponds to your use case. But the root of the problem will still be the closure, if your problem is even remotely similar to what you presented. In this case, you can use bind instead, to attach the loop variable's value to the function without it being closed over:

kiraСharacteristicsArray = ["Goddess", "Ideal", "Perfection"]

for kiraСharacteristic from kiraСharacteristicsArray

    setTimeout ((innerKiraСharacteristic) ->
        console.log "Kira is #{innerKiraСharacteristic}!"
    ).bind(null, kiraСharacteristic), 0

Now, this is not a particularly CoffeeScript answer, as neither setTimeout nor bind are CoffeeScript-specific, but it is written in pure CoffeeScript. Whether there is a better way in CS2, I can't say.