DEV Community

Cover image for Await Your Loops
K
K

Posted on • Edited on

Await Your Loops

Cover image by Dakota Ray on Flickr

If you read stuff about JavaScript lately, you probably already know that it gets new features every now and then. One of those are Asynchronous Iterations.

You probably already know about iterables and async/await, but if not don't worry, I'll update you first.

Iterables

Iterables are objects that have a method in the Symbol.iterator field that returns an object with a next() method. This can be used to get all iterable values of that object.

In case of an array, an object which is JS-built-in, it looks like this:

const a = [1, 2, 3];
const iteratorOfA = a[Symbol.iterator]();

iteratorOfA.next(); // { value: 1, done: false }
iteratorOfA.next(); // { value: 2, done: false }
iteratorOfA.next(); // { value: 3, done: false }
iteratorOfA.next(); // { value: undefined, done: true}

Enter fullscreen mode Exit fullscreen mode

The nice thing is, you can use it in a for-in-loop, without all the extra syntax.

const a = [1, 2, 3];
for(let i in a) console.log(i); 

Enter fullscreen mode Exit fullscreen mode

But yes, this isn't too exiting, basic JavaScript stuff.

The cool part is, that you can write your own iterables:

const iterable = {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
  [Symbol.iterator]: function() {
    const keys = Object.keys(this);
    let i = 0;
    return {
      next: () => {
        if (i == keys.length) return {value: null, done: true};
        return {
          value: [keys[i], this[keys[i++]]],
          done: false
        };
      }
    }
  }
};

for(let item of iterable) console.log(item);
Enter fullscreen mode Exit fullscreen mode

Object.keys() only returns the non-symbol keys as an array, so Symbol.iterator won't show up in it.

Now, when the next method of that returned object is called, I return a new key and value pair as an array. When I don't find any more pairs, I return an object with done: true and tell the caller that I'm finished.

As you can see in the end, this new object can be used like an array in a for-in-loop.

Async/Await

A not-so-basic JavaScript feature, which is rather recent are asynchronous functions, or async/await.

Essentially it adds syntax sugar to promises.

Without async/await it would look like that:

function load(url) {
  return fetch(url)
  .then(response => response.json())
  .(json => json.data);
}
Enter fullscreen mode Exit fullscreen mode

And with you get to write code that looks synchronous again:

async function load(url) {
  const response = await fetch(url);
  const json = await response.json();
  return json.data;
}
Enter fullscreen mode Exit fullscreen mode

Asynchronous Iterations

As you can probably imagine, there are quite some asynchronous operations that won't be done with just one promise.

But you can't simply write something like that:

function processRows(filePath) {
  for(let row of getRow(filePath)) {
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Because the getRow() call would hit the file-system, which is an asynchronous operation. You would have to read the whole file, before you could get the single rows for processing.

Or it could be a server call that ends up being paginated and you would have to send multiple of them to get all pages.

But now there is an proposal for that too!

Instead of using Symbol.iterator and using a next-method that returns your values, you use Symbol.asyncIterator and use a next-method that returns promises of those values.

const asyncIterable = {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
  [Symbol.asyncIterator]: function() {
    const keys = Object.keys(this);
    let i = 0;
    return {
      next: () => {
        if (i == keys.length) return Promise.resolve({value: null, done: true});
        return Promise.resolve({
          value: [keys[i], this[keys[i++]]],
          done: false
        });
      }
    }
  }
};

async function process() { 
  for await (let item of asyncIterable) console.log(item);
}

process();

Enter fullscreen mode Exit fullscreen mode

And we're back to code that can be sprinkled with try/catch and all the other nice synchronous features.

As you can see, you can simply return a promise that resolves to an object with done: true if your iterable data is finished. Like, when the server doesn't return anything anymore.

In that example the data is in-memory, but could come from anywhere now.

Conclusion

Asynchronous iterables are another step in making async/await syntax and promises more ingrained into JavaScript. They ease the work of writing asynchronous code by making it look more and more synchronous.

An alternative, which I mentioned in other posts too, are observables like provided by RxJS.

Top comments (4)

Collapse
 
clickclickonsal profile image
Sal Hernandez

I didn't know about Iterables until I read this! This is awesome! Thank you!

Note: In the example, you left out the () to execute the a[Symbol.iterator].

It should be.
const iteratorOfA = a[Symbol.iterator]();

Collapse
 
kayis profile image
K

Yes you're right, thanks!

Collapse
 
khawar_jatoi profile image
Khawar Jatoi

Thanks a lot, I needed this.

Collapse
 
albinotonnina profile image
Albino Tonnina

Where was this article two days ago, when I needed it!
Well, it was here :D
Thanks for the article!