Go channels in JS (4/5): Ranging
Tags: javascript, go, tutorial, node
Published: 2019-12-23 23:15:22

This post is the fourth of a series about how I wrote in JavaScript the equivalent of Go(lang) channels.

If you haven't already, I highly recommend reading at least the first post before reading this one:

{% post nlepage/go-channels-in-js-1-5-sending-and-receiving-2lci %}

So far we have built an equivalent of Go channels in JS, which allows us to create channels, buffered or unbuffered, send values to these, receive values from these, and finally close these.

This time we will use the closing feature we added last time as a base to build a new feature: Ranging.

First, let's have a look at how to range over a channel in Go.

## Ranging over channel

If you remember last time, we used the ability of the receive operator to return two values in order to know if a channel has been closed:

```go
for { // This is like while (true)
  i, ok := <-ch
  if !ok {
    break
  }
  fmt.Println(i)
}
```

The second value returned by the receive operator is a boolean which tells us if something has actually been received.

However Go offers a better syntax to do the exact same thing, which is [the range operator](https://tour.golang.org/concurrency/4):

```go
for i := range ch {
  fmt.Println(i)
}
```

This range loop will iterate over the values received from `ch` until this one is closed.

Let's take back our `send123()` example:

```go
func main() {
  ch := make(chan int) // Create an integer channel

  go send123(ch) // Start send123() in a new goroutine

  // Receive an integer from ch and print it until ch is closed
  for i := range ch {
    fmt.Println(i)
  }
}

func send123(ch chan int) {
  // Send 3 integers to ch
  for i := 1; i <= 3; i++ {
    ch <- i
  }

  close(ch) // Close ch
}
```

This is much easier to read than last time!
Now how could we transpose this in JS?

Of course using a [`for ... of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) would be nice.
But `for ... of` uses the [iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterator_protocol) which is synchronous, whereas the receive operation is asynchronous.

Good news, JS has asynchronous iteration since ES2018, which comes with a new [`for await ... of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) syntax.
So we could create a new range operation, which would return an [asynchronous iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator).

Let's try this with our `send123()` example:

```js
async function* main() {
  const ch = yield chan() // Create a channel

  yield fork(send123, ch) // Start send123()

  // Receive a value from ch and log it to console until ch is closed
  for await (const i of yield range(ch)) {
    console.log(i)
  }
}

function* send123(ch) {
  // Send 3 integers to ch
  for (let i = 1; i <= 3; i++) {
    yield send(ch, i)
  }

  yield close(ch) // Close ch
}
```

Nice! Like in Go the code is much easier to understand, even if having a `yield` inside a `for await ... of` is not simple.

Now let's implement our new range operation!

## Implementing ranging over channel

As usual we need an operation factory:

```js
const RANGE = Symbol('RANGE')
export const range = chanKey => {
  return {
    [RANGE]: true,
    chanKey,
  }
}
```

We have only one `chanKey` argument which is the key of the channel we want to iterate over.

Then we have to handle range operations in the channel middleware:

```js
export const channelMiddleware = () => (next, ctx) => async operation => {
  // ...

  if (operation[RANGE]) {
    // Handle range operation
  }

  // ...
}
```

`for await ... of` needs an asynchronous iterable, which is an object able to return an iterator.
A common pattern is to use the same object as iterable and iterator:

```js
if (operation[RANGE]) {
  const it = {
    [Symbol.asyncIterator]: () => it,
  }

  return it
}
```

As you can see `it` returns itself when asked for an iterator, and will therefore satisfy the async iterable protocol.
Now `it` needs to implement the async iterator protocol.

All the async iterator protocol needs is an async `next()` function, which must return an object with two properties:
 - `value` which is the current value
 - `done`, a boolean which tells us if the iterator has ended, in which case `value` may be omitted

This looks a lot like the detailed receive operation we created last time, which returns a scalar with a value and a boolean which tells us if a value was actually received.
The only actual difference is that the boolean is inverted.

So we should be able to use the `doRecv()` function we created last time to implement `next()`:

```js
const it = {
  [Symbol.asyncIterator]: () => it,

  next: async () => {
    const [value, ok] = await doRecv(ctx, operation.chanKey)

    return {
      value,
      done: !ok,
    }
  }
}
```

And this is it! Well that was easy.
Let's try this out on repl.it with our `send123()` example (it uses [esm](https://github.com/standard-things/esm) to benefit from modules):

{% replit @nlepage/Go-channels-in-JS-45-Ranging %}

## What next

With ranging done, we are not far from having the full feature set of channels, the only thing missing is [select](https://tour.golang.org/concurrency/5).

The last post "Go channels in JS (5/5): Selecting" will be a big one, I'll need some time to write it...
In the meantime I might publish some bonus posts (did you know that Go allows receiving from a nil channel?), so stay tuned.

I hope you enjoyed this fourth post, give a ❤️, 💬 leave a comment, or share it with others, and follow me to get notified of my next posts.