claj
claj

Reputation: 5412

More elegant way to handle error and timeouts in core.async?

Of course I want to wrap various requests to external services with core.async, while still returning results from these operations through some chan.

I want to take care of both thrown exceptions and timeouts (ie that the operation takes longer than expected to return, or to be able to choose among various services for the same task but with different approaches or qualities of service.

The smallest viable example to show examples of both being able to handle an error, a timeout and a correct returning result seems to be these:

(require '[clojure.core.async :refer [chan go timeout <! >! alt!]])

(def logchan (chan 1))

(go (loop []
      (when-let [v (<! logchan)]
        (println v)
        (recur))))

(dotimes [_ 10] 
  (go 
    (let [result-chan  (chan 1)
          error-chan   (chan 1)
          timeout-chan (timeout 100)]
      (go
        (try 
          (do (<! (timeout (rand-int 200)))
              (>! result-chan (/ 1 (rand-int 2))))
          (catch Exception e (>! error-chan :error))))
      (>! logchan (alt! [result-chan error-chan timeout-chan] 
                    ([v] (if v v :timeout)))))))

This code prints something like

1
:error
1
:error
:error
:timeout
:error
:timeout
:timeout

This is not very elegant. I especially don't like the way of returning :error and :timeout. The nil-check in alt! is clearly not what I want either.

Is there some better way to accomplish the three goals of returning result, protect from long timeouts and handle errors? The syntax is quite OK (most things above are really to provoke those three errors).

Upvotes: 4

Views: 1287

Answers (2)

claj
claj

Reputation: 5412

The core.async chan-function has the ex-handler, so the following construct is possible

(chan buf-or-n xform ex-handler)

where ex-handler is a one-argument function that gets the exception. When the function returns nil, this is not put on the channel, otherwise the function has the chance to transform the exception to something viable for the data in question.

Upvotes: 6

edbond
edbond

Reputation: 3951

Put output in one channel, errors and results.

Define records (maps) for errors such as

(defrecord SomeError [cause context etc...])

This way the errors will hold relevant info.

Verify output in consumers

(if-not (instance? SomeError result) ...)

There is no one 'correct' way, you can use pub/sub for the same purpose, pass in one error-chan or create several and async/merge them together.

Upvotes: 2

Related Questions