// Apps Script

Replace Rhino for each loops with for...of.

Rhino's for each...in iterates values, but the tempting quick fix — dropping 'each' to get for...in — iterates string keys instead. Here is why that silently corrupts arithmetic, and how for...of is the correct replacement.

I migrated a script from Rhino to V8 and now I'm getting a syntax error on my for each loop, and I'm not sure what the safe replacement is.

The script

copy · paste · trigger
sumColumn.gs
Apps Script
// Sum a column of numbers — Rhino vs V8 comparison
// Run sumWithForOf() after enabling V8 runtime

function getColumnValues() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var range = sheet.getRange('B2:B10');
  return range.getValues().map(function(row) { return row[0]; });
}

// BROKEN: for...in iterates keys '0','1','2'...
// total ends up as '00123' (string concat)
function sumBroken() {
  var values = getColumnValues();
  var total = 0;
  for (var i in values) { total += values[i]; }
  Logger.log(total);
}

// CORRECT: for...of iterates the actual numeric values
function sumWithForOf() {
  var values = getColumnValues();
  var total = 0;
  for (var v of values) { total += v; }
  Logger.log(total);
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

What Rhino's for each actually did

Rhino (Apps Script's old JavaScript engine) implemented a non-standard Mozilla extension: `for each (var x in collection)` iterated over the *values* of an array or object, not its keys. It was never part of any ECMAScript standard, and V8 — the engine Apps Script switched to in 2020 — simply does not parse it. The syntax error you see when you enable V8 is not a migration quirk; Rhino's for each was always proprietary.

The confusion starts because the fix looks obvious: just delete the word 'each'. That gives you `for (var x in collection)`, which V8 accepts without complaint. The script runs. The logs look plausible. And then at some point you notice a sum came back as a string like '00.50.2511.75' instead of a number, or a conditional that should have branched on a value is branching on '0' or '3' instead.

Why for...in produces the wrong result on arrays

The `for...in` statement enumerates an object's *enumerable property keys*. For an array, those keys are the index strings: '0', '1', '2', and so on. You get a string, not the element sitting at that position. If you then do arithmetic — `total += values[i]` where `i` is the string '0' — JavaScript coerces `values['0']` correctly (array bracket notation accepts string indices), so the value looks right in isolation. The silent failure is when `total` starts as the number `0` and the first addition is `0 + someNumber`: that works. But if the cell contains a number and the coercion chain breaks elsewhere, or if you were iterating an object rather than an array, '0' as a key gives you something completely different from what Rhino's for each gave you.

I hit this the first time on a reporting script that summed invoice amounts across nine sheets. The log showed a figure that was just plausible enough to pass a quick glance — it was the string concatenation of all the values, which happened to start with zero, so it looked like a large integer. It took a type-check (`typeof total`) to catch it.

The correct replacement and when forEach fits instead

Replace `for each (var x in arr)` with `for (var x of arr)`. The `for...of` statement, introduced in ES6 and fully supported under V8, iterates over the *values* of any iterable — arrays, strings, Maps, Sets, generator results, and the return value of `getValues()` once you flatten it. It is the semantic equivalent of Rhino's for each on arrays.

If the loop body has no early exits (`continue` / `break` / `return`), `arr.forEach(function(x) { ... })` is also a clean option and reads well. Reach for `for...of` when you need `break` or `return` inside the loop body, since `forEach` callbacks do not propagate those to the outer function. One pattern that catches engineers: `getValues()` returns a 2D array (an array of rows, each row an array of cells). Rhino's for each on that iterated over row arrays. `for...of` does the same. If you want individual cells, you need a nested loop or a prior `.flat()` call — and that was true under Rhino too.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
I enabled V8 and now I get 'SyntaxError: Unexpected identifier' on my for each line. What do I change?
Replace `for each (var x in arr)` with `for (var x of arr)`. That is a direct value-iterating equivalent. Do not use `for...in`; it iterates index strings, not values.
My script already uses for...in and it seems to work. Is there actually a bug?
It depends on what you do inside the loop. If you access values as `arr[i]` where `i` is the loop variable, you get the correct element because array bracket notation accepts string indices. If you use `i` directly as a value (arithmetic, comparison, passing it to a function expecting a number), you are operating on a string like '0' or '3', which may silently coerce or produce wrong results.
Does for...of work on the 2D array that getValues() returns?
Yes. Each iteration gives you one row array (e.g., `[cellA, cellB, cellC]`). To reach individual cells, nest a second `for...of` loop or call `.flat()` on the result before iterating.
Can I use for...of on a plain object, like a config map?
No. Plain objects are not iterable in ES6. For an object, use `for (var key in obj)` to get keys, or `Object.values(obj)` wrapped in a `for...of` to get values: `for (var v of Object.values(obj))`.