Foreach Loops and Timeouts in 3 Languages

I’m currently working on an IoT project that has components written in a variety of languages. Primarily we’re using TypeScript, C#, and Squirrel (the Electric Imp variety).

During the project, we discovered a bug in the Squirrel code. The cause turned out to be a foreach loop that was doing the equivalent of the JavaScript setTimeout (but in ElectricImp Squirrel it’s imp.wakeup) with the callback referencing the current value in each iteration. But when the callback actually executed, they all referenced the last element in the array.

I’ve definitely encountered this before in JavaScript. But I realized that it’s been long enough since I’d seen it that I’d forgotten about this “gotcha.” For a great writeup on the problem, check out Thrown for a loop: understanding for loops and timeouts in JavaScript.

I thought it would be interesting to see how each language I’m using handles the scenario where a setTimeout-like asynchronous callback is triggered inside of a loop.

Squirrel (Electric Imp)

This is the scenario that led to the bug I mentioned earlier: a foreach loop with an imp.wakeup call inside the loop. Only a single val variable is created, and the value assigned to it is changed in each iteration. Since the callback is closing over val, by the time any of the callbacks are invoked the val variable will contain “f,” and that’s all that will print out.


local vals = ["a", "b", "c", "d", "e", "f"];
foreach (val in vals) {
  imp.wakeup(0, function() {
      server.log("Val: " + val);
  });
}
Val: f
Val: f
Val: f
Val: f
Val: f
Val: f

To get the desired behavior, where the server.log prints out each character after a short delay, you can assign a local variable inside the loop. It will then be closed over by the callback.


local vals = ["a", "b", "c", "d", "e", "f"];
foreach (val in vals) {
  local v = val;
  imp.wakeup(0, function() {
      server.log("Val: " + v);
  });
}
Val: a
Val: b
Val: c
Val: d
Val: e
Val: f

TypeScript / JavaScript

An initial attempt at recreating the problem in TypeScript was unsuccessful – each character was printed out after the delay:


const vals = ["a", "b", "c", "d", "e", "f"];
for (const val of vals) {
  setTimeout(() => console.log(`Val: ${val}`), 500);
}
Val: a
Val: b
Val: c
Val: d
Val: e
Val: f

However, when replacing the const with var, the problem surfaced. This makes sense when you understand the difference between var, let and const. Where var is scoped to the whole function and let and const are block-scoped.


const vals = ["a", "b", "c", "d", "e", "f"];
for (var val of vals) {
  setTimeout(() => console.log(`Val: ${val}`), 500);
}
Val: f
Val: f
Val: f
Val: f
Val: f
Val: f

C#

C# doesn’t have a built-in function like setTimeout. But, thanks to the power of Stack Overflow, I was able to throw together a reasonable facsimile based on this answer:


private static void SetTimeout(Action fn, uint delay)
{
    var timer = new Timer { Interval = delay, AutoReset = false };
    timer.Elapsed += (sender, e) => fn();
    timer.Start();
}

As with TypeScript, my initial attempt with C# resulted in the “correct” behavior, with the closure capturing each letter:


var vals = new List { "a", "b", "c", "d", "e", "f" };
foreach (var val in vals)
{
  SetTimeout(() => _testOutputHelper.WriteLine($"Val: {val}"), 500);
}
Val: c
Val: b
Val: e
Val: a
Val: d
Val: f

However, when I tried an old fashioned for loop, the value of i was not “captured.” This would cause the code to crash. This is because all of the callbacks would try to access the array with an index that was too large. So this one just prints out the value of i.


var vals = new List { "a", "b", "c", "d", "e", "f" };
for (var i = 0; i < vals.Count; i++)
{
  SetTimeout(() => _testOutputHelper.WriteLine($"i: {i}"), 500);
}
Val: 6
Val: 6
Val: 6
Val: 6
Val: 6
Val: 6

It’s always good to be careful when using closures inside of a loop. Both TypeScript/JavaScript and C# have made doing this type of thing much less problematic by the way they scope variables inside the loop (if you use foreach in C#, and const/let in TypeScript/JavaScript).

Unfortunately for those of us doing any Squirrel development, that is not the case in a Squirrel foreach loop.

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *