Futures in Go
blog.charmes.net
blog.charmes.net
You should return a chan. In this case, all crawlers should be returning along the same chan, errors or results, rather than creating one per request. Then you A: don't have a problem where you could be processing result #5 but result #3 is blocking right now, increasing the total latency, B: having multiple consumers using the same channel is trivial and at the user's discretion, and C: the resulting chan can participate in a "select" of the user's choosing, unlike a function call. There is no way in which using this approach is superior to using a chan.
If you look at the wikipedia page for "futures", which has a lot more than just any one community's idea of "futures" in it, you can see that a channel that you expect to read one value from is, if not necessarily exactly any one of the given definitions, certainly a family member. It isn't in the type system, but it is still the case that if you have a need for a "future" in Go you're better off with a chan with that constraint documented, rather than returning a function that can be called, because of the inability to select on such a function. http://en.wikipedia.org/wiki/Futures_and_promises
Create a new goroutine and block on the action. You can continue working in another goroutine and communicate between them using channels.
The advantage of this programming model over futures/promises is straight-line code that is easier to read and reason about. For example, when bugs appear you get a nice stack trace that reveals the state of the call. Future leave you in a callback-like mess.
See http://blog.golang.org/pipelines for more details.
It seems to be because each goroutine has a single parent (which launched it), so there is typically a single reader for its return value.
Generally, futures/promises just solve the problem of how to simulate blocking within an event-loop-driven (i.e. cooperatively-scheduled) platform, e.g. Node. When actor-processes are cheap, your consumer can be an actor which just actually blocks on a result—and then continues execution in the same linear block of code when that result arrives.
To put it another way: in non-actor languages, you'd see a block of code like this...
do_random_other_work();
bfn_args = some_setup();
blocking_fn(bfn_args).then(function(result) {
console.log(result);
});
more_random_other_work();
...while in actor-modelled languages, it'd go more like this: do_random_other_work();
spawn(function() {
bfn_args = some_setup();
result = blocking_fn(bfn_args);
console.log(result);
});
more_random_other_work();
Note how you have a closure in both cases, but they contain different parts of the code. The actor-modelled closure contains the setup work of sending the message, as well as the work of dealing with the reply—which must be linear with respect to one-another, but which can all happen concurrently to the random other work.As well, notice that the actor-modelled version of blocking_fn is actually a plain old blocking function—a synchronous-IO read, for example. The blocking_fn in the promise-modelled code, on the other hand, is a special function which must be defined using a promises library, and must do the work of ensuring its asynchronicity at definition-time, whether or not a given consumer wants to call the function in an async way relative to its own thread of execution.
In short, idiomatic actor-modelled code puts the consuming programmer in control of which tasks are linear and which are concurrent, which usually makes for succinct, reusable plainly-synchronous functions that Get Things Done, and higher-level glue code that represents all the policy of what concurrency happens where. Promises throw out all these advantages and mix policy with mechanism in a way that increases verbosity and decreases reusability.
But there are cases where you really want multiple readers. For example, on a cache miss, you want a single goroutine to do the work to fill the cache, but there may be multiple goroutines waiting on the result. In this case it might seem natural to model the cache internally as a map from keys to futures, even if it's not exposed?
The way to do it would be closer to something like that:
do_random_other_work();
actor = spawn(function() {
bfn_args = some_setup();
result = blocking_fn(bfn_args);
emit(result);
});
result = wait_for_result(actor)
more_random_other_work(result); This site or app is sending too much traffic to
rawgit.com. Please contact its owner and ask them to use
cdn.rawgit.com instead, which has no traffic limit.In Go, each value on a channel goes to a single reader and it's rare to need multiple readers. One way to do it might be to return a channel that receives an infinite stream of the same value.
The correct way to do this is to return results on a chan of (error, response) regardless of how much you "don't like it" for unspecified reasons.
var (
ch = make(chan *http.Response)
err error
)
....
return func() (*http.Response, error) {
return <-ch, err
}
It seems to me like calling that returned function is destructive -- if you block on it more than one, it would block forever. Is that right? It seems like a different contract than the usual Future / Promise shtick.