Go channels in JS (Bonus 1): nil channel
Tags: javascript, go, tutorial, node
Published: 2020-01-08 07:14:54

This is a bonus post in the "Go channels in JS" series about how I wrote in JavaScript the equivalent of Go(lang) channels.
If you haven't already, I recommend reading at least the first post before reading this one:

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

So did you know that Go allows using `nil` channels?
Now let's see how and why this is possible.

## `nil` channel

Let's start by clarifying what is a `nil` channel.

So far when we wanted to create a channel, we used the `make` builtin function:

```go
ch := make(chan int)
```

`make` returns a pointer to a channel, so a `nil` channel is just a `nil` pointer, in other words no channel at all:

```go
// This a nil integer channel pointer:
var ch chan int

// Declared in a more explicit way:
var ch chan int = nil
```

So why would we need a `nil` channel?

You would think that sending to or receiving from a `nil` channel is an illegal operation, but it is actually allowed.
_Both will block indefinitely!_

Now the next question is how is this useful?
We don't want a goroutine to be blocked forever, this is actually a well known problem: a goroutine leak.

Well there is the [`select`](https://tour.golang.org/concurrency/5) statement we haven't spoken of so far, which allows to wait for several channel operations at the same time:

```go
func PrintValues(ints chan int, strings chan string) {
	for {
		select {
		case i := <-ints:
			fmt.Printf("Received integer: %d\n", i)
		case s := <-strings:
			fmt.Printf("Received string: %s\n", s)
		}
	}
}
```

But what if the sender closes the `ints` channel?
Receiving from a closed channel returns a `nil` value, so `PrintValues` will print `"Received integer: 0"` on the standard output indefinitely!

In order to avoid that, it is possible to use a `nil` channel to disable one `case` of the `select`:

```go
func PrintValues(ints chan int, strings chan string) {
	for {
		select {
		case i, ok := <-ints:
			if !ok {
				ints = nil
				break
			}
			fmt.Printf("Received integer: %d\n", i)
		case s := <-strings:
			fmt.Printf("Received string: %s\n", s)
		}
	}
}
```

As soon as the `ints` channel is closed, we replace it by a `nil` pointer, which disables the first `case` of the `select`.

Of course we have to do the same for the `strings` channel, but it would end up blocking the entire `select`, and the goroutine executing it...
`PrintValues` must return when both channels are closed:

```go
func PrintValues(ints chan int, strings chan string) {
	for {
		select {
		case i, ok := <-ints:
			if !ok {
				if strings == nil {
					return
				}
				ints = nil
				break
			}
			fmt.Printf("Received integer: %d\n", i)
		case s, ok := <-strings:
			if !ok {
				if ints == nil {
					return
				}
				strings = nil
				break
			}
			fmt.Printf("Received string: %s\n", s)
		}
	}
}
```

[Run it on Go playground](https://play.golang.org/p/vkcuvkYfg3R)

Now that we know what `nil` channels may be used for, let's add the same feature to our JS channels.

## Implementing `nil` channels

As our JS channels don't have a `select` for now, our implementation of `nil` channels will be partial.

The equivalent of a `nil` channel in JS will be a `null` or `undefined` channel.

So far when we created or executed send and receive operations, we didn't check at all that the channel key was actually defined or different of `null`.

Hence sending to or receiving from a `null`/`undefined` channel would have ended up in a `TypeError` somewhere in our code.

Now let's modify the existing send operation in order to accept `null`/`undefined` channel keys and return a never resolved `Promise`:

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

  if (operation[SEND]) {
    if (!operation.chanKey) return new Promise(() => {})

    // Actually perform send operation...
  }

  // ...
}
```

The receive operation uses the `doRecv()` function, which is mutualized with the range operation (see previous post).
So let's modify the `doRecv()` function to also accept `null`/`undefined` channel keys and return a never resolved `Promise`:

```js
const doRecv = async (ctx, chanKey) => {
  if (!chanKey) return new Promise(() => {})

  // Actually perform receive operation...
}
```

And that's it!
Of course, we just implemented the "bad part" of `nil` channels, and we will have to add the good part next time when implementing the `select`...

## What next

Next time we will finally implement the [select](https://tour.golang.org/concurrency/5), and complete the full feature set of channels.

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