2

what is the best way to guard ES6's for ... of loop?

for (let bar of foo.bars) {
  baz = await quix(bar)
}

what if bars is undefined? It throws

TypeError: undefined is not iterable!

A simple

if (foo.bars) {
  // ..
}

is the way to do it?

user3568719
  • 1,036
  • 15
  • 33
  • Why *would* `bars` be `undefined`? – Bergi Aug 06 '17 at 14:56
  • 1
    I think that your two problems aren't related. For example, an error is also thrown if you try to iterate with `for...in` over an undefined value. Not providing actual value to statements or declarations might have a unexpected results. – gion_13 Aug 06 '17 at 14:58
  • Depending upon your situation, you may want to guard against `foo.bars` being more than `undefined`. It could also be something that isn't iterable and that would throw. – jfriend00 Aug 06 '17 at 16:19

3 Answers3

4

Yes, make sure in some way that foo.bars is not undefined before the loop. How to do that depends on your actual use case of course.

If you have some kind of nullable field in your object, an if test would be the cleanest way indeed. A less obvious alternative is iterating an empty collection as a defaulting value:

for (let bar of foo.bars || []) {
  baz = await quix(bar)
}

However, the best practice probably would be to have the empty collection instead of null (or undefined) as the property value right away.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Wouldn't a more complete guard, need to see if `foo.bars` is actually iterable because your solution leaves plenty of values for `foo.bars` that will still throw. – jfriend00 Aug 06 '17 at 15:51
  • @jfriend00 As I said, it always depends on the use case - in a good program you *know* what possible values it can have at which time :-) Also [there's no good iterable test](https://stackoverflow.com/a/41559281/1048572), so the most complete guard would be `try { for (let bar of foo.bars) { … } } catch(e) { … }` anyway. – Bergi Aug 06 '17 at 16:11
  • It depends upon where this data comes from. If you're an interface that foreign programmers are using, then you have no idea what the possible values are and an explicit test would allow you to provide a better error than just letting it throw a somewhat confusing error. Yes, I know about the iterable test since that was an answer to my own question. It seems worth adding that as an option here to make an even better answer. – jfriend00 Aug 06 '17 at 16:15
  • 1
    @jfriend00 The question only asked about it "being undefined", I think the rest is well handled by the first paragraph of the answer, our discussion here and the link to your question – Bergi Aug 06 '17 at 16:17
0

If you want to be safer

if (foo && "bars" in foo) {...}
José Quinto Zamora
  • 2,070
  • 12
  • 15
0

A complete guard to protect against any possible data that might cause an exception at the beginning of the for/of loop would have to test three things:

  1. That foo exists
  2. That foo.bar exists
  3. That foo.bar is iterable

There is no super clean way of testing all three:

if (foo && foo.bar && typeof foo.bar[Symbol.iterator] === "function") {
    for (let bar of foo.bar) {
        baz = await quix(bar);
    }
}

More info on testing to see if something is an iterable here: What is the technical definition of a Javascript iterable and how do you test for it?

You could, of course, just use a try/catch around it and catch any exception rather than pre-testing for it (which you may need anyway to handle rejections in the await).

jfriend00
  • 683,504
  • 96
  • 985
  • 979