ponyfoo.com

JavaScript Asynchronous Iteration Proposal

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

There’s a proposal in the works to simplify asynchronous iteration even further.

The Asynchronous Iteration proposal is already in stage 2 and being heavily worked on.

Before diving into this article, you may want to brush up on a number of concepts:

Shall we take a look?

For context, let’s start with what we know. You may recall how iterators work using Symbol.iterator as an interface to define how an object is to be iterated.

const ponyfoo = {
  [Symbol.iterator]: () => {
    const items = [`p`, `o`, `n`, `y`, `f`, `o`, `o`];
    return {
      next: () => ({
        done: items.length === 0,
        value: items.shift()
      })
    }
  }
}

And that the ponyfoo object can be iterated in a number of different ways: such as using the spread operator, Array.from, or for..of, among others.

[...ponyfoo]
// <- [`p`, `o`, `n`, `y`, `f`, `o`, `o`]
Array.from(ponyfoo)
// <- [`p`, `o`, `n`, `y`, `f`, `o`, `o`]

for (const item of ponyfoo) {
  console.log(item)
  // <- `p`
  // <- `o`
  // <- `n`
  // <- `y`
  // <- `f`
  // <- `o`
  // <- `o`
}

The contract to an iterator mandates that the next method of Symbol.iterator instances returns an object with value and done properties. The value property indicates the current value in the sequence, while done is a boolean indicating whether the sequence has ended.

In async iterators, the contract changes a little bit: next is supposed to return a Promise that resolves to an object containing value and done properties. Instead of reusing the same Symbol, a new Symbol.asyncIterator is introduced to declare asynchronous iterators.

For the purposes of our demonstration, the ponyfoo iterable could be made iterable asynchronously with two small changes, we ditch Symbol.iterator in favor of Symbol.asyncIterator, and we wrap the return value for the next method in Promise.resolve, returning a Promise.

const ponyfoo = {
  [Symbol.asyncIterator]: () => {
    const items = [`p`, `o`, `n`, `y`, `f`, `o`, `o`];
    return {
      next: () => Promise.resolve({
        done: items.length === 0,
        value: items.shift()
      })
    }
  }
}

Naturally, that was quite a contrived example. Another contrived example could be a utility function that fetches a series of HTTP resources sequentially.

const getResources = endpoints => ({
  [Symbol.asyncIterator]: () => ({
    i: 0,
    next () {
      if (endpoints.length <= this.i) {
        return Promise.resolve({ done: true })
      }
      return fetch(endpoints[this.i++])
        .then(response => response.json())
        .then(value => ({ value, done: false }))
    }
  })
})

In order to consume an async iterator, we can leverage the for await..of syntax that would also be introduced by this proposal. This way, there’s yet another way of writing code that looks synchronous yet behaves asynchronously.

const resources = [
  `/api/users`,
  `/api/testers`,
  `/api/hackers`,
  `/api/nsa-backdoor`
];

for await (const data of getResources(resources)) {
  console.log(data);
}

There’s also async generator functions in this proposal. An async generator function is just like a generator function, but also supports await and for await..of declarations.

async function* getResources(endpoints) {
  for (endpoint of endpoints) {
    const response = await fetch(endpoint)
    yield await response.json()
  }
}

When called, async generators return an { next, return, throw } object whose methods return promises for { next, done }, instead of returning { next, done } directly.

You can consume the getResources async generator in exactly the same way you could consume the object-oriented async iterator.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments