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:
Go channels in JS (1/5): Sending and Receiving
Nicolas Lepage for Zenika ・ Dec 2 '19
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:
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:
// 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
statement we haven't spoken of so far, which allows to wait for several channel operations at the same time:
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
:
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:
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)
}
}
}
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
:
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
:
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, 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.