DEV Community

Cover image for The Iterators Are Coming! Iterator and asyncIterator in JavaScript
Nested Software
Nested Software

Posted on • Edited on • Originally published at nestedsoftware.com

The Iterators Are Coming! Iterator and asyncIterator in JavaScript

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 }
    }
}  
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Let's try it:

C:\dev>node iter.js
value = 3
value = 1
value = 4
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (2)

Collapse
 
pyrokar profile image
Gunter Solf

Thanks for the great introduction in this new feature 👍

And now I need an real world example / problem where this is the solution 🤔

Collapse
 
nestedsoftware profile image
Nested Software • Edited

Thank you! There is a simple example at github.com/tc39/proposal-async-ite... which has to do with reading lines asynchronously from a file.