API design advice with blocks

NATS implemented key/value support inside streams (closer to etcd than Redis) so I’m trying to build support for it into my NATS client (branch). One of the features involves watching for changes to a key’s value:

bucket.watch "key" do |entry|
  # Do something with the entry
end

At some point, you probably want to stop watching for changes to that key, though, so I’m not sure how I’d like to implement that. It’s currently implemented on the branch to yield a NATS::KV::Watch instance whose only purpose is to stop watching alongside the NATS::KV::Entry instance:

bucket.watch "key" do |entry, watch|
  # Do something with the entry

  watch.stop if we_should_stop_now?
end

I was hoping to implement this with a break but it’s implemented as a captured block (it’s the subscription handler block for messages in a stream), so apparently that’s off the table.

So I guess, I’m trying to understand:

  • Would you expect this block to run synchronously?
  • How does it make sense to stop it?

It’s an event handler, so it can’t really run synchronously. It’s triggered by some external action.

I don’t follow in your example why you would stop from inside the handler. That means you only get a chance to stop listening when it’s triggered. I would imagine that you should be able to stop listening externally as well, for example following a user command. That would be something like bucket.stop_watching("key") (which could also be called from inside the handler btw.).

Maybe I’m missing something and the continue-or-not question must be answered inside the block. Then you could consider making the block return value indicate that. For example make it Bool and true means continue, false means stop.

I return an object from the equivalent watcher in etcd that you can call #stop upon.

It’s not a particularly elegant way of going about things, but it does the job.