Async Iteration with JavaScript

John Au-Yeung - Feb 1 '20 - - Dev Community

Subscribe to my email list now at http://jauyeung.net/subscribe/

Follow me on Twitter at https://twitter.com/AuMayeung

Many more articles at https://medium.com/@hohanga

Even more articles at http://thewebdev.info/

Asynchronous code is a very important part of JavaScript. We need to write lots of asynchronous code, since JavaScript is a single-threaded language. If we run everything line by line, then code that takes longer to run will hold up the program, causing it to freeze.

To write asynchronous code easily, JavaScript has promises, which are chainable and can be run in sequence without nesting.

ES2017 introduced the async and await syntax for chaining promises, which makes everything easier. However, there was still no way to run promises sequentially, iterating them in sequence. This means that running lots of promises in a sequence is still a problem with no solution.

Fortunately, in ES2018, we finally have a for-await-of loop that we can use with async functions that were introduced in ES2017. It works with any iterable objects, like the synchronous for...of loop. This means that we can iterate through objects like Maps, Sets, NodeLists, and the arguments object with it.

Also, it can any object with the Symbol.iterator method, which returns a generator to let us do the iteration.


for-await-of Loop

We can use the for-await-of loop as follows:


let promises = [];
for (let i = 1; i <= 10; i++) {
  promises.push(new Promise((resolve) => {
    setTimeout(() => resolve(i), 1000);
  }));
}
(async () => {
  for await (let p of promises) {
    const val = await p;
    console.log(val);
  }
})();

In the example above, we added ten promises by defining them in the for loop and then pushing them to the promises array. Then we run the promises by defining an async function and then using the for-await-of loop to loop through each promise and run them one at a time.

The await in the for-await-of loop will get us the resolved value from the promise and the console.log will log the resolved value.

Like other async functions, it can only return promises.

The syntax of the for-await-of loop is the same as any other for...of loop. The let p is the variable declaration and promises is the promises array we’re iterating through.

The code above works like Promise.all, the code is run in parallel, so we see one to ten after one second.


Async Iterables

Alternatively, we can define our own asynchronous iterable object if we define an object with the Symbol.asyncIterator method. It’s a special method that returns a generator that lets us iterate through it with the for-await-of loop.

For example, we can write:

let asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      i: 1,
      next() {
        if (this.i <= 10) {
          return new Promise((resolve) => {
            setTimeout(() => resolve({
              value: this.i++,
              done: false
            }), 1000);
          });
        }
        return Promise.resolve({
          done: true
        });
      }
    };
  }
};
(async () => {
  for await (let p of asyncIterable) {
    const val = await p;
    console.log(val);
  }
})();

The code above will return a generator, which returns a new promise as the for-await-of loop runs.

In the Symbol.asyncIterator method, we have a next function, which is required if we want to make an asynchronous iterable object. Also, we have the i property to keep track of what we iterated through.

In the promise that we return, we have the setTimeout with a callback function that resolves to an object with the value we want to resolve, and the property done. The done property is false if the iterator should keep on returning new values for the for-await-of loop and true otherwise.


Async Generator

We can make the code shorter with the yield keyword to make a generator function to return a generator. Generators are another kind of iterable object which can be used with the for-await-of loop.

For example, we can write:

The keyword function* denotes that our function only returns a generator. async means that the generator runs asynchronously. Inside the asyncGenerator function, we have the yield keyword. The yield keyword gets us the next item from the generator as we loop through the generator with the for-await-of loop.

The yield keyword only works with top-level code. It doesn’t work inside callback functions.

If we run the code above, we should get one to ten outputted.

If we write something like this:

async function* asyncGenerator() {  
  for (let i = 1; i <= 10; i++) {  
    setTimeout(() => {  
      yield i;  
    }, 1000);  
  }  
}

We’ll get a SyntaxError.

An object can be made iterable with if we define a Symbol.asyncIterator method which is set to a generator function.

For example, we can write:

async function* asyncGenerator() {
  for (let i = 1; i <= 10; i++) {
    yield i;
  }
}
(async () => {
  for await (let p of asyncGenerator()) {
    const val = await p;
    console.log(val);
  }
})();

Then we get one to ten logged in the console.log output.

It works like the asyncIterable object we defined above, except that the code is much shorter. Also, we can’t use the yield keyword on callbacks so we have to write it as we have above in that case.

Finally, we can write the following to loop through promises directly:

The await keyword actually waits for each promise to resolve until running the next one, so we get each number showing up after one second when we run the code.

Asynchronous iteration solves the promise of running many promises sequentially. This feature is available in ES2018 or later with the introduction of the for-await-of loop and asynchronous iterators and generators. With these the issue of iterating through asynchronous code has been solved.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .