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 (9)

Michael J. Ryan wrote

In the last sentence I think you mean “object oriented asyncIterator” …?

Michael J. Ryan wrote

Nm I misread… My fault for reading on my phone without glasses on.

Scato Eggen wrote

I sort of gathered that there is a competing proposal: Observable. Do you know which one is winning?

And what about transpilation? I didn’t find a transpiler that converts for await…of. Do you know of one?

just nobody wrote

I’d recommend against using array.shift() for the examples, since any array you feed into the functions will be systematically destroyed! A shame if you wanted to reuse that list of endpoints…

At least copy the array first!

async function* getResources(endpoints) {
  endpoints = endpoints.slice()
  while (endpoints.length > 0) {
    const endpoint = endpoints.shift()
    const response = await fetch(endpoint)
    yield await response.json()
  }
}
Nicolás Bevacqua wrote

Yeah, and using for endpoint of endpoints would be even better! 😅

Mohan wrote

This looks like something I would use right now. Would the async generator work with Array.from() or array destruction.

isbdnt wrote

The last await is not necessary, because return value will always be resolved.

vsemozhetbyt wrote

“…whose methods return promises for { next, done }, instead of returning { next, done } directly.”

Could it be “{ value, done }, instead of returning { value, done } directly” instead?