2
votes

I am trying to validate a form using core.async by making a request to a validation function every time the form changes. The validation function is asynchronous itself. It hits an external service and returns either an array of error messages or an empty array.

(go-loop []
   (when-let [value (<! field-chan)]
      (go (let [errors (<! (validate value))]
             (put! field-error-chan errors)))))

The above code is what i have at the moment. It works most of the time, but sometimes the response time from the server changes so the second request arrives before the first. If the value is not valid in the second case but valid the first time we would pull an array of errors followed by an empty array off the field-error-chan.

I could of course take the validation out of a go loop and have everything returning in the correct order, but, I would then end up taking values from the field-chan only after checking for errors. What I would like to do is validate values as they come but put the validation response on the errors channel in the order the value came not the order of the response.

Is this possible with core.async if not what would be my best approach to getting ordered responses?

Thanks

2
Does validate reurn a chan onto which the errors will be written? Can you include more about how validate works?Arthur Ulfeldt

2 Answers

1
votes

Assuming you can modify the external validation service, the simplest approach would probably be to attach timestamps (or simply counters) to validation requests and to have the validation service include them in their responses. Then you could always tell whether you're dealing with the response to the latest request.

Incidentally, the internal go form serves no purpose and could be merged into the outer go-loop. (Well, go forms return channels, but if the go-loop is actually meant to loop, this probably isn't important.)

0
votes

You can write a switch function (inspired by RxJs):

(defn switch [in]
  (let [out (chan)]
    (go (loop [subchannel (<! in)]
      (let [[v c] (alts! [subchannel in])]
        (if (= c subchannel)
          (do (>! out v) (recur subchannel))
          (recur v)))))
    out))

Then wrap the field-chan function and

(let [validate-last (switch (async/map validate [field-chan])]
  ...)

But note that the switch does not handle closing channels.