I'm not too familiar with core.async, but from what I've read, I know I can do stuff like this:
;; fetch data
(defn get-data-from-server
[]
(let [ch (chan)]
(fetch-data-from-server (fn [result]
(put! ch result)))
ch))
;; echo data
(go-loop []
(let [v (<! (get-data-from-server))]
(.log js/console v)))
Assume I need to get fresh data from server for every 1000ms, this is what I did:
(defn get-data-from-server
[]
(let [ch (chan)]
(.setInterval js/window
(fetch-data-from-server (fn [result]
(put! ch result))) 1000)
ch))
After some time it complains something like No more than 1024 pending put on a single channel, consider using ..... Any suggestion?
Why are you creating a brand new channel every time your go-loop runs? Perhaps you should not create the channel inside the go loop and only reference an already-created channel. That tends to be much more common and better performant.
Related
The question is how to send to a nodejs app the result of a go block
i found a solution with callback
but i need a solution with promise
Promise solution?
Clojurescript app
(defn foo []
(go 1))
;;how to change foo,wrap to promise?, so node app can await to get the 1
;;i used 1 for simplicity in my code i have something like
;;(go (let [x (<! ...)] x))
Node app
async function nodefoo() {
var x = await foo();
console.log(x); // i want to see 1
}
Callback solution (the one that works now)
So far i only found a way to pass a cb function, so this 1 goes back to node.js app
Clojurescript app
(defn foo1 [cb]
(take! (go 1)
(fn [r] (cb r))))
Node app
var cb=function () {....};
foo1(cb); //this way node defined cb function will be called with argument 1
But i dont want to pass a callback function, i want node.js to wait and get the value.
I want to return a promise.
This function takes a channel and returns a Javascript Promise that resolves with the first value the channel emits:
(defn wrap-as-promise
[chanl]
(new js/Promise (fn [resolve _]
(go (resolve (<! chanl))))))
Then to show usage:
(def chanl (chan 1))
(def p (wrap-as-promise chanl))
(go
(>! chanl "hello")
(.then p (fn [val] (println val))))
If you compile that and run it in your browser (assuming you called enable-console-print!) you'll see "hello".
It is also possible to extend the ManyToManyChannel type with extend-type.
Here's a naif implementation using a similar wrap-as-promise function
(require '[clojure.core.async.impl.channels :refer [ManyToManyChannel]])
(defn is-error? [val] (instance? js/Error val))
(defn wrap-as-promise
[channel callback]
(new js/Promise
(fn [resolve reject]
(go
(let [v (<! channel)]
(if (is-error? v)
(reject v)
(resolve (callback v))))))))
(extend-type ManyToManyChannel
Object
(then
[this f]
(wrap-as-promise this f)))
(def test-chan (chan 1))
(put! test-chan (new js/Error "ihihi"))
(put! test-chan :A)
(defn put-and-close! [port val]
(put! port val)
(async/close! port))
(-> test-chan
(.then (fn [value] (js/console.log "value:" value)))
(.catch (fn [e] (js/console.log "error" e)))
(.finally #(js/console.log "finally clause.")))
I try to deasync some js-calls using clojure script. If I let the atom in the deasync function itself there is a suprising result:
(let [result (atom nil)
timeout (atom 10)]
(go
(let [async-result (<p! (js/Promise.resolve 42))]
(swap! result (constantly async-result))
(js/console.log #result)))
(while (and (nil? #result)
(pos? #timeout))
(do (debug #result)
(debug #timeout)
(swap! timeout dec)))
#result)
--- output is ---
null
10
...
null
1
42
nil
If I define the atom outside the result is as intended:
(def result (atom nil))
(let [timeout (atom 10)]
(go
(let [async-result (<p! (js/Promise.resolve 42))]
(swap! result (constantly async-result))
(js/console.log #result)))
(while (and (nil? #result)
(pos? #timeout))
(do (debug #result)
(debug #timeout)
(swap! timeout dec)))
#result)
--- output is ---
42
42
Can someone declare the difference?
I think using a namespace global atom is not quite a good idea for a deasync function ...
It is not possible to write a "deasync" function, if by that you mean writing a function that "blocks" to wait for an async result. JavaScript is single-threaded so if you block in a loop to wait for an async result that loop will prevent the async work from ever happening.
The result in your program above will always be that it loops 10 times while the result is nil and then eventually yield control to let the queued microtask execute the go.
I suspect that you just maybe tried to run this from the REPL and did not redefine the result atom after one test run. So in the first pass it was nil but then got set to 42. On the second run it then starts with 42 and your loop never loops. You can easily verify this by "labeling" your results. So instead of (js/console.log #result) you use (js/console.log "go-result" #result). You'll see that you'll always get the loop result first and then the go-result.
BTW if you just want to set a specific atom value just use reset! instead of swap! with constantly, eg. (reset! result async-result).
I'm displaying a set of questions for a quiz test and I'm assigning a number to each question just to number them when they are shown in the browser:
(defn questions-list
[]
(let [counter (atom 0)]
(fn []
(into [:section]
(for [question #(re-frame/subscribe [:questions])]
[display-question (assoc question :counter (swap! counter inc))])))))
The problem is that when someone edits a question in the browser (and the dispatch is called and the "app-db" map is updated) the component is re-rendered but the atom "counter" logically starts from the last number not from zero. So I need to reset the atom but I don't know where. I tried with a let inside the anonymous function but that didn't work.
In this case I'd just remove the state entirely. I haven't tested this code, but your thinking imperatively here. The functional version of what your trying to do is something along the lines of:
Poor but stateless:
(let [numbers (range 0 (count questions))
indexed (map #(assoc (nth questions %) :index %) questions)]
[:section
(for [question indexed]
[display-question question])])
but this is ugly, and nth is inefficient. So lets try one better. Turns out map can take more than one collection as it's argument.
(let [numbers (range 0 (count questions))
indexed (map (fn [idx question] (assoc question :index idx)) questions)]
[:section
(for [question indexed]
[display-question question])])
But even better, turns out there is a built in function for exactly this. What I'd actually write:
[:section
(doall
(map-indexed
(fn [idx question]
[display-question (assoc question :index idx)])
questions))]
Note: None of this code has actually been run, so you might have to tweak it a bit before it works. I'd recommend looking up all of the functions in ClojureDocs to make sure you understand what they do.
If you need counter to be just an index for a question, you could instead use something like this:
(defn questions-list
[]
(let [questions #(re-frame/subscribe [:questions])
n (count questions)]
(fn []
[:section
[:ul
(map-indexed (fn [idx question] ^{:key idx} [:li question]) questions)]])))
Note: here I used [:li question] because I assumed that question is some kind of text.
Also, you could avoid computing the count for questions in this component and do it with a layer 3 subscription:
(ns your-app.subs
(:require
[re-frame.core :as rf]))
;; other subscriptions...
(rf/reg-sub
:questions-count
(fn [_ _]
[(rf/subscribe [:questions])])
(fn [[questions] _]
(count questions)))
Then in the let binding of your component you would need to replace n (count questions) with n #(re-frame/subscribe [:questions-count]).
I have an array of art pieces. I want to find the route length and associate it with each art pieces.
My code will look like:
(defn load-art-routes [art-list ctx]
(doall (map-indexed (fn [index art]
(let [user-location (select-keys (:coords (sub> ctx :geolocation)) [:latitude :longitude])
art-location (:geoLocation art)]
(->> (map-request {:origin (str (:latitude user-location) "," (:longitude user-location))
:destination (str (:lat art-location) "," (:lon art-location))
:mode (name (sub> ctx :transition-mode))})
(p/map (fn [data]
(let [route-length (ocall js/Math "round" (/ (get-in data [:routes 0 :legs 0 :distance :value]) (* 0.621371192 1000)) 2)
route-duration (ocall js/Math "floor" (/ (get-in data [:routes 0 :legs 0 :duration :value]) 60))]
(js/console.log "load-art-routes route-length " route-length")
(assoc art :route-length route-length))))
(p/error (fn [error]
(util/log (str "GOOGLE DIRECTIONS API ERRORS" params) error)
))))) art-list))
art-list)
(defn map-request [params]
(when params
(let [endpoint google-directions-api-endpoint]
(->> (make-get-req (str endpoint "&" (encode-query-params params))
{})
(p/map (fn [data]
(util/log "GOOGLE DIRECTIONS API " data)
data))
(p/error (fn [error]
(util/log (str "GOOGLE DIRECTIONS API ERRORS" params ) error)
))))))
The route length calculation is correct but, assoc is not working. It is not actually associating it. I don't know what the issue is. Can anyone help me?
Please simplify this example! In the process, you will discover the problem.
First, update your question to include the require that shows what p/map, p/error, etc are. Also, put map-request before load-art-routes just like it must be in your source file.
Then, you should start by removing the thread-last ->> operator and use let with names for intermediate values:
(let [aa (map-request ...)
bb (p/map (fn [data] ...) aa)
cc (p/error (fn [error] ...) bb) ]
<bb or cc here?> )
My suspicion is that your p/error call is swallowing the results of p/map and returning nil.
This looks like you are trying to write "mutable" code.
Reformatting the code and fixing one error makes this more obvious:
(defn load-art-routes [art-list ctx]
(doall (map-indexed (fn [index art]
(let [user-location (select-keys (:coords (sub> ctx :geolocation)) [:latitude :longitude])
art-location (:geoLocation art)]
(->> (map-request {:origin (str (:latitude user-location) "," (:longitude user-location))
:destination (str (:lat art-location) "," (:lon art-location))
:mode (name (sub> ctx :transition-mode))})
(p/map (fn [data]
(let [route-length (ocall js/Math "round" (/ (get-in data [:routes 0 :legs 0 :distance :value]) (* 0.621371192 1000)) 2)
route-duration (ocall js/Math "floor" (/ (get-in data [:routes 0 :legs 0 :duration :value]) 60))]
(js/console.log "load-art-routes route-length " route-length)
(assoc art :route-length route-length))))
(p/error (fn [error]
(util/log (str " GOOGLE DIRECTIONS API ERRORS " params) error)
))))) art-list))
art-list)
load-art-routes simply returns the original art-list and kicks of some work in promises. These promises only update their versions of the list but given that we are using immutable data structures the returned art-list themselves remain unchanged. There is also a suspicious second art-list in the p/error call?
You'll probably want to restructure this to something that either returns a Promise or accepts a callback that will be called once all route-length have been computed.
Consider the following hypothetical nonsensical ClojureScript function:
(defn tmp []
(def p 0)
(set! p (inc p))
(set! p (inc p))
(set! p (inc p)))
Repeatedly executing this function in a REPL results in
3
6
9
etc.
Is it possible to create a mutable variable which is local to the function, such that the output would have been
3
3
3
etc.
in the case of repeated exection of (tmp)?
let lets you assign variables limited to it's scope:
(defn tmp[]
(let [p 0]
...))
Now, clojurescript makes use of immutable data. That means everything is basically a constant, and once you set a value of p, there is no changing it. There are two ways you can get around this:
Use more local variables
(defn tmp[]
(let [a 0
b (inc a)
c (inc b)
d (inc c)]
...))
Use an atom
Atoms are somewhat different from other data structures in clojurescript and allow control of their state. Basically, you can see them as a reference to your value.
When creating an atom, you pass the initial value as its argument. You can access an atoms value by adding # in front of the variable, which is actually a macro for (deref my-var).
You can change the value of an atom using swap! and reset! functions. Find out more about them in the cljs cheatsheet.
(defn tmp[]
(let [p (atom 0)]
(reset! p (inc #p))
(reset! p (inc #p))
(reset! p (inc #p))))
Hope this helps.