Elixir tip: :noreply is a killer feature

If you’ve ever looked at the GenServer documentation, you might have noticed something strange.

At least, this looked very strange to me at first. Why, I wondered, would you ever want to return {:noreply, new_state} in a handle_call/3 callback?

Shhhhhh

After all, a call to a GenServer that returns {:noreply, new_state} will generate a timeout and crash your program.

It took a while before something clicked and I understood the utility of :noreply for handle_call/3 callbacks. If you read further in the documentation, you’ll see this little nugget:

Returning {:noreply, new_state} does not send a response to the caller and continues the loop with new state new_state. The response must be sent with reply/2.

This means that if you use GenServer.reply(from, reply), you can return {:noreply, new_state} and the caller will still get a response. At first glance this doesn’t seem to be that useful, but let’s explore some of the possibilities.

Doing expensive work “out of band”

It might make sense sometimes to reply early to the caller if there is some expensive work that you want to do that the caller doesn’t actually have to wait for.

Reply early and do expensive work “out of band”.

This will allow the caller to receive the reply it’s waiting for early and continue sooner with its work.

Note: since Elixir 1.7 you should use {:continue, term()} instead.

Processing other messages before replying

Sometimes you don’t have the information you need to return at the moment handle_call/3 is called. In that case, you can return {:noreply, new_state} and reply only when you have the correct information.

Send a reply later and process other messages in the meantime.

Now we are starting to see some of the power of :noreply. The caller will wait (until it times out) for the reply it needs, but the GenServer it’s calling into can continue to process messages. This is basically a way to make asynchronous processing look like synchronous processing. We have used this pattern to make calls to an “asynchronous” API look synchronous.

Client sees its request being processed synchronously.

Delegating replying to another process

The coolest part of GenServer.reply/2 is that you don’t have to reply from the process that originally received the call. If you are delegating work to another process, you might as well let that process reply to the call and save yourself a couple of messages.

Responding to a call from another process.

This is a great result because it means that we don’t have to burden the GenServer in the middle with the task of responding to the call. This will allow that GenServer to do less work and reduce the likelihood of it becoming a bottleneck.

Making the caller wait

This is the last usage of :noreply that I will share in this blog post. Sometimes it can be useful to make the caller wait until some other process has completed some work, to avoid overloading the system. Then you can delay replying until all the work is done, passing around the from reference as necessary.


Unfortunately for you, :noreply is only for GenServers! Share this tip with your friends and colleagues!

Like what you read? Give Derek Kraan a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.