javascript – Checking whether something is iterable-ThrowExceptions

Exception or error:

In the MDN docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for…of

The for...of construct is described to be able to iterate over “iterable” objects. But is there a good way of deciding whether an object is iterable?

I’ve tried to find common properties for arrays, iterators and generators, but have been unable to do so.

Aside for doing a for ... of in a try block and checking for type errors, is there a clean way of doing this?

How to solve:

The proper way to check for iterability is as follows:

function isIterable(obj) {
  // checks for null and undefined
  if (obj == null) {
    return false;
  }
  return typeof obj[Symbol.iterator] === 'function';
}

Why this works (iterable protocol in depth): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Since we are talking about for..of, I assume, we are in ES6 mindset.

Also, don’t be surprised that this function returns true if obj is a string, as strings iterate over their characters.

###

Why so verbose?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'

###

The simplest solution is actually this:

function isIterable (value) {
  return Symbol.iterator in Object(value);
}

Object will wrap anything which isn’t an object in one, allowing the in operator to work even if the original value is not an Object. null and undefined are turned into empty objects so there’s no need for edge case detection, and strings get wrapped into String objects which are iterable.

###

As a sidenote, BEWARE about the definition of iterable. If you’re coming from other languages you would expect that something you can iterate over with, say, a for loop is iterable. I’m afraid that’s not the case here where iterable means something that implements the iteration protocol.

To make things clearer all examples above return false on this object {a: 1, b: 2} because that object does not implement the iteration protocol. So you won’t be able to iterate over it with a for...of BUT you still can with a for...in.

So if you want to avoid painful mistakes make your code more specific by renaming your method as shown below:

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);

###

Nowadays, as already stated, to test if obj is iterable just do

obj != null && typeof obj[Symbol.iterator] === 'function' 

Historical answer (no more valid)

The for..of construct is part of the ECMASCript 6th edition Language Specification Draft. So it could change before the final version.

In this draft, iterable objects must have the function iterator as a property.

You can the check if an object is iterable like this:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}

###

For async iterators you should check for the ‘Symbol.asyncIterator’ instead of ‘Symbol.iterator’:

async function* doSomething(i) {
    yield 1;
    yield 2;
}

let obj = doSomething();

console.log(typeof obj[Symbol.iterator] === 'function');      // false
console.log(typeof obj[Symbol.asyncIterator] === 'function'); // true

###

If you wanted to check in fact if a variable is an object ({key: value}) or an array ([value, value]), you could do that:

const isArray = function (a) {
    return Array.isArray(a);
};

const isObject = function (o) {
    return o === Object(o) && !isArray(o) && typeof o !== 'function';
};

function isIterable(variable) {
    return isArray(variable) || isObject(variable);
}

Leave a Reply

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