The Iterators Are Coming! Iterator and asyncIterator in JavaScript
Asynchronous Iterators and Generators (2 Part Series)
Introduction
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)
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:
C:\dev>node iter.js
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, ournext
method now returns a promise that resolves into a{value, done}
tuple. - Also, we now implement a
Symbol.asyncIterator
function instead ofSymbol.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 markedasync
, I’ve moved the driving code into anasync
main
function. I’ve also added a timer so that we can clearly see that thefor 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:
- for await…of
- AsyncIterator
- Iteration protocols
for...of
- Symbol
- Object initializer
- Asynchronous iterators
- ES2018: asynchronous iteration
Related: