The Iterators Are Coming! [Symbol.iterator] and [Symbol.asyncIterator] in JavaScript

April 17, 2018 0 Comments

The Iterators Are Coming! [Symbol.iterator] and [Symbol.asyncIterator] in JavaScript

 

 

This article goes over two kinds of iterators in JavaScript: Synchronous and asynchronous. The former has been a part of JavaScript for a while. The latter is coming soon in ES2018.

The iteration protocol in JavaScript is pretty basic. For the synchronous version, we just need to define a next function that returns a tuple with a value and a done flag. For example:

class SimpleIterable { next() { return { value: 3, done: true } } 
}

However, a number of constructs in JavaScript expect an "iterable," and just having a next function isn't always good enough. The for...of syntax is a case in point. Let's try to use for...of to loop over one of our SimpleIterable objects:

const iter = new SimpleIterable() 
for (const value of iter) { console.log('value = ' + value)
}

The result is:

C:\dev>node iter.js 
C:\dev\iter.js:8
for (const value of iter) { ^ TypeError: iter is not iterable at Object.<anonymous> (C:\dev\iter.js:8:21) at Module.compile (module.js:660:30) at Object.Module.extensions..js (module.js:671:10) at Module.load (module.js:573:32) at tryModuleLoad (module.js:513:12) at Function.Module.load (module.js:505:3) at Function.Module.runMain (module.js:701:10) at startup (bootstrapnode.js:190:16) at bootstrap_node.js:662:3

Synchronous Iterators

We can fix this by supplying a special function. The function is identified by the symbol, Symbol.iterator. By adding it to our class, we can make our iterable work with for...of:

class SimpleIterable { next() { return { value: 3, done: true } } Symbol.iterator { return { next: () => this.next() } } 
}

What is this [Symbol.iterator] syntax? Symbol.iterator is a unique identifier (see Symbol ). Putting it in square brackets makes it a computed property name. Computed properties were added in ES2015 (see Object initializer ). The documentation provides this simple example:

// Computed property names (ES2015)
var prop = 'foo';
var o = {
[prop]: 'hey',
['b' + 'ar']: 'there'
};
console.log(o) // { foo: 'hey', bar: 'there' }

Let's try it again:

That fixed our error, but we're still not outputting our value. It looks as though for...of ignores the value once it encounters a true done flag.

Let's make our example slightly more elaborate by actually iterating over a small array of values. When we exceed the bounds of our array, our value will become undefined and our done flag will be set to true:

class SimpleIterable { constructor() { this.index = 0 this.values = [3,1,4] } next() { const value = this.values[this.index] const done = !(this.index in this.values) this.index += 1 return { value, done } } Symbol.iterator { return { next: () => this.next() } } } const iter = new SimpleIterable() 
for (const value of iter) { console.log('value = ' + value)
}

Let's try it:

C:\dev>node iter.js 
value = 3
value = 1
value = 4

Great, it worked!

Asynchronous Iterators

Currently, JavaScript's iterators are synchronous, but asynchronous iterators are coming in ES2018. They are already implemented in recent versions of node, and we can play with them using the --harmony-async-iteration flag. Let's modify our existing example to use asynchronous iterators:

const timer = () => setInterval(()=>console.log('tick'), 500) class SimpleAsyncIterable { constructor() { this.index = 0 this.values = [3,1,4] } next() { const value = this.values[this.index] const done = !(this.index in this.values) this.index += 1 return new Promise( resolve=>setTimeout(()=>resolve({ value, done }), 1000)) } Symbol.asyncIterator { return { next: () => this.next() } } } const main = async () => { const t = timer() const iter = new SimpleAsyncIterable() for await (const value of iter) { console.log('value = ' + value) } clearInterval(t) } main() 

What’s different?


  • We can see that instead of just returning a {value, done} tuple, our next method now returns a promise that resolves into a {value, done} tuple.

  • Also, we now implement a Symbol.asyncIterator function instead of Symbol.iterator.

  • The syntax of for...of has been changed into an asynchronous form: for await...of.

Since we can only use await in a function that is marked async, I've moved the driving code into an async main function. I've also added a timer so that we can clearly see that the for await...of construct is non-blocking.

Let's see our asynchronous iterable in action:

C:\dev>node --harmony-async-iteration asyncIter.js 
tick
value = 3
tick
tick
value = 1
tick
tick
value = 4
tick
tick

Great, it worked! We can see that for await...of uses Symbol.asyncIterator to await each promise. If the done flag is false, for await...of will then retrieve the value on each iteration of the loop. Once it hits an object with a done flag of true, the loop ends.

It is possible to achieve a similar effect using synchronous iterators by returning a promise for the value attribute. However, done would not be asynchronous in that case.

In an upcoming article, I will write a detailed examination of asynchronous generator functions, which can be used with this new for await...of syntax.

References:

Related:


Tag cloud