I consider this a Go anti-pattern. Without a good reason, do not try to provide "asynchronousness" in a library. Go natively supports goroutines and channels, and it's considered baseline skill in the language to be able to fire something off in a goroutine (it's literally a two-character keyword) and receive something on a channel, if you want to. It would be not only adequate but preferable to implement this library as a fully synchronous request system and expect the user of the library to implement what asynchronousness they may require. Your hardwiring of exactly how the "asynchronousness" works may conflict with my own needs.
All the "asynchronousness" you need to provide is already hard-wired into the Go runtime itself; what certain language communities have trained you to think is "synchronous" code already isn't. You don't have to super-duper-extra make it even more asynchronous.
I won't quite call exposing channels in a library API a code smell, it's a little too useful for that, but it's still something where you ought to pause for a moment and really think about what it means, especially if you created it rather than receiving it.
Oh, and let me be clear: Levigross, I'm seriously suggesting that you change this library wholesale to be fully synchronous, and the fact that this will be an API change is a feature, not a bug. You'll find it also simplifies the API significantly, also a feature.
Thank you for the suggestion. I plan on adding some functionality to make asynchronous APIs friendlier to use (like a `Do` or `Apply` function etc...). But if you don't choose to use the asynchronous APIs everything still functions the same (in fact the asynchronous APIs use the synchronous functions and slap channels on them)
Oh, what the heck, I'll name names. This is part of why I've been so pissy at the Node community for the past few years. Their definition of synchronous is wrong, and it's really become quite popular. Synchronous does not mean "blocks the whole process". That is a weakness of Node, not a universal programming truth. From the Node perspective, all Go code is always and automatically asynchronous. You don't have to add it. It already is. You're not adding functionality by trying to "nicely" provide an asynchronous API, you're simple redundantly adding what is already there, and what you're adding is significantly less flexible than what Go already provides.
(Now with more correct definitions of the terms, it is meaningful to say that Go code is synchronous within a goroutine. But Go already provides abundant tools for dealing with that within its runtime. We can already compose these tools together trivially to do whatever async patterns we want.)
Now, I suppose, standard disclaimer, this is your library, do as you like. I'm not actually personally passionate about this like I'm yelling at my monitor or anything. I'm trying to help you save time and effort. It's up to you what you decide to do.
1. Thank you for the feedback
As you have already seen, I submitted my library to /r/golang and have gotten a lot of feedback – from which I modified the constructs (originally the functions returned channels).
I want to write something that is useful to as many people as possible (all while not alienating anyone) and therefore try not to force users (like I originally did) to use the "asynchronous APIs".
I didn't expect this to end up on HN and was going to start a discussion on golang-nuts on the pros and cons of this construct. Based on that, I was going to remove or keep the APIs.
wat?
† At least in 1.1, I couldn't write a fast port-scanner without doing my own scheduling, but that's a corner case.
Kind of a meta question, but an interesting one.
Part of the problem is that even as an attempt at a "non-synchronous" interface, this seems clumsy. It demand-creates new channels for requests. That doesn't seem right.
It might make sense for a library to return a new channel for something you do once, or once per goroutine.
It doesn't make sense to use returned channels as a basis for async dispatch.
Using http://golang.org/pkg/reflect/#Select to build what you need;
Or building something specific to your use case without the above, which seems likely to be... goofy and unmanageable. Creating objects that create new channels to do `select`s over some fixed number of cases and composing those.
Do you have any experience using `reflect.Select`? While not exactly ergonomic, it seemed a... pretty okay tradeoff, inconvenience for power, and at the very least more power for approximately the same amount of pain as the rest of the `reflect` package.
You can do it, but since the only reason to do it is to avoid spawning goroutines, I'm not sure why you'd bother.