funcool

A channel for discussing and asking questions about Funcool libraries https://github.com/funcool/
martinklepsch 2017-01-02T09:45:06.000192Z

@niwinz decided to give potok a shot, now wondering I have something that returns a promise, do you have an example how I would use that with WatchEvent?

niwinz 2017-01-02T09:45:32.000193Z

yes, just coerce it to reactive stream (rx/from-promise promise)

niwinz 2017-01-02T09:46:25.000195Z

In uxbox I have get rid of promises

niwinz 2017-01-02T09:46:41.000196Z

http and everything async related is now usig rx

martinklepsch 2017-01-02T09:47:30.000197Z

don’t like promises anymore? 😄

1
niwinz 2017-01-02T09:48:07.000198Z

I like them but they are not silver bullet, rx observables are designed from the ground up to separate composition from execution

niwinz 2017-01-02T09:48:40.000199Z

they enables better resource management...

niwinz 2017-01-02T09:49:38.000200Z

as you know, http requests are cancellable, and managing http request cancellation with promises is awful.

niwinz 2017-01-02T09:52:59.000201Z

I have the pending task of release the httpurr library with promises replaced by observables 😛

martinklepsch 2017-01-02T09:54:32.000202Z

I see, interesting perspective. You really are into observables haha

martinklepsch 2017-01-02T09:55:00.000203Z

I searched the beicon docs for “promise” and similar but the from-promise thing didn’t show up

niwinz 2017-01-02T09:58:17.000205Z

not all api is documented with examples

martinklepsch 2017-01-02T09:58:56.000206Z

ah ok, I thought the API docs were embedded in that page somewhere

martinklepsch 2017-01-02T09:58:58.000207Z

sorry 🙂

martinklepsch 2017-01-02T10:02:20.000208Z

So I now created a stream from the promise, now what’s the right way to do something when it succeeds/fails? For failures I use rx/catch I guess? For success I use rx/map? 😄

martinklepsch 2017-01-02T10:06:58.000209Z

And another question: let’s say I want to log something for every event — How would I do that?

martinklepsch 2017-01-02T10:09:30.000210Z

The beicon merge example shows concat: http://funcool.github.io/beicon/latest/#merge

martinklepsch 2017-01-02T10:12:43.000211Z

Something that I think is really cool about re-frame is the ability to swap effect handlers for testing easily — have you developed any patterns to do similar things with potok?

niwinz 2017-01-02T10:15:13.000212Z

I didn't need it or at least I'm not clearly understand the need of it.

niwinz 2017-01-02T10:15:52.000213Z

If I don't want to some event to execute, I just redef it's constructor "Var"

niwinz 2017-01-02T10:16:44.000214Z

the watch event, should return a stream of events, so you need is rx/map

niwinz 2017-01-02T10:16:46.000215Z

over value

martinklepsch 2017-01-02T10:16:54.000216Z

So for stubbing out an event you just redef it?

niwinz 2017-01-02T10:17:01.000217Z

and return a instance of event that transforms the state

martinklepsch 2017-01-02T10:17:19.000218Z

I now ended up with something like this

(rx/merge (rx/just (->UiStart (hash this)))
              (->> (rx/from-promise
                    (-> (js/firebase.database)
                        (.ref (str (name type)))
                        (.push (clj->js (get-in state (into [::ui] form-path))))))
                   (rx/delay 4000)
                   (rx/map (fn [x] (->UiDone (hash this))))))))

which does pretty much what I need

niwinz 2017-01-02T10:19:18.000220Z

That looks good if you need that

niwinz 2017-01-02T10:20:04.000221Z

merge is not sequential, if you need to emit events in strictly order, rx/concat can be used

niwinz 2017-01-02T10:21:02.000222Z

later, the functions implements the update event, so you can emit them as is, without creating additional types

martinklepsch 2017-01-02T10:22:15.000223Z

> later, the functions implements the update event, so you can emit them as is, without creating additional types Can you expand, I don’t think I understand

niwinz 2017-01-02T10:22:46.000225Z

a plain cljs functions implements the UpdateEvent, so you can emit them as is

martinklepsch 2017-01-02T10:22:47.000226Z

@niwinz order is not super important, just that an event is put on the stream as it becomes available

niwinz 2017-01-02T10:23:29.000227Z

(defn increment [state] (update state :counter inc))

(ptk/emit! store increment)

martinklepsch 2017-01-02T10:25:05.000231Z

Interestingly when I replace merge with concat in the above snippet the UiDone event is processed before UiStart

niwinz 2017-01-02T10:25:39.000233Z

hmm strange

niwinz 2017-01-02T10:26:10.000234Z

have you put the trace logs in the impl or in constructors ?

martinklepsch 2017-01-02T10:26:54.000235Z

ptk/UpdateEvent
  (update [_ state]
    (js/console.log :UiStart (update-in state [::ui ::progress] (fnil conj #{}) op-id))
    (update-in state [::ui ::progress] (fnil conj #{}) op-id)))

niwinz 2017-01-02T10:28:05.000236Z

😞 maybe is a bug in concat

martinklepsch 2017-01-02T10:29:08.000237Z

I’m sorry to be the conveyor of bad news 😄

niwinz 2017-01-02T10:29:14.000238Z

hehe

niwinz 2017-01-02T10:29:47.000239Z

on the contrary, you are helping!

martinklepsch 2017-01-02T10:30:01.000240Z

Do you have any suggestion regarding the “log on every event” hook thing?

niwinz 2017-01-02T10:30:55.000241Z

at this moment, potok does not comes with builtin solution.

niwinz 2017-01-02T10:30:58.000242Z

However,

niwinz 2017-01-02T10:31:47.000243Z

On uxbox, I define a own emit! function that removes the need constantly pass the store on all the code

niwinz 2017-01-02T10:32:05.000244Z

and I have put some traces in this function for log every emitted event.

niwinz 2017-01-02T10:32:42.000245Z

a own emit! function is just like a partial application function but with traces 😄

martinklepsch 2017-01-02T10:33:01.000246Z

yeah, that seems to be a sensible approach

martinklepsch 2017-01-02T10:33:06.000248Z

thanks

martinklepsch 2017-01-02T10:34:40.000249Z

The question about testing/swapping “handlers” is still bugging me a bit but I’ll think about it some more

niwinz 2017-01-02T10:35:21.000250Z

As I understand, in reframe the event and the implementation is splitted

martinklepsch 2017-01-02T10:35:33.000251Z

I think it’s a tradeoff of attaching the implementation to the message instead of having a kind-of registry that defines implementation

martinklepsch 2017-01-02T10:35:42.000252Z

yeah, exactly

niwinz 2017-01-02T10:36:06.000253Z

do you know specify! macro?

martinklepsch 2017-01-02T10:36:18.000254Z

heh, that’s what I was just thinking about

niwinz 2017-01-02T10:37:14.000255Z

on the other hand, the system is pretty flexible, I usually define events that does async stuff and other that does state transformation, each one with own constructors

niwinz 2017-01-02T10:37:38.000256Z

if I want to mock some event that is emited indirectly

martinklepsch 2017-01-02T10:37:47.000257Z

I guess I could implement a registry that does

{:some-event (fn [ev-id ev-args]
               (reify
                 WatchEvent
                 (watch ....)))}

niwinz 2017-01-02T10:37:55.000258Z

I just, redef the var of the constructor for it.

martinklepsch 2017-01-02T10:37:58.000259Z

not specify! infact but kind of similar

niwinz 2017-01-02T10:38:40.000260Z

yeah, you can implement a own registry like stuff

martinklepsch 2017-01-02T10:39:11.000261Z

I think that’s an acceptable solution to this thought experiment for now 🙂

niwinz 2017-01-02T10:39:23.000263Z

I have experimented in the past with splitted aproach (event and impl) and I found it too much mental overhead

martinklepsch 2017-01-02T10:40:09.000264Z

How is it too much overhead? (Not used to the direct approach...)

niwinz 2017-01-02T10:40:58.000266Z

in the last project when I have worked, the event and the impl was splited, events are defined as static constants and implementation are using some kind of register

niwinz 2017-01-02T10:41:06.000267Z

the implementation are in one file

niwinz 2017-01-02T10:41:16.000268Z

and events and event constructors are in other

niwinz 2017-01-02T10:41:55.000269Z

wen you are working with complex flows,

niwinz 2017-01-02T10:42:56.000270Z

this becomes very painful, constantly navigating through different files to understand the all the flow

niwinz 2017-01-02T10:43:45.000271Z

having the event and impl in the same location, reduces considerably this pain, just reducing the number of files/namespaces to inspect.

niwinz 2017-01-02T10:45:13.000273Z

in any case, potok is more than simple join of event and impl. the fact of using rx, helps on synchronize complex flows in a more "digestible" code

niwinz 2017-01-02T10:45:41.000274Z

is my opinion

martinklepsch 2017-01-02T10:56:22.000275Z

I think I understand what you mean but just technically splitting them doesn’t mean they need to be in different places. I can use a registry that refers to implementations that are located right where they’re used

niwinz 2017-01-02T10:59:21.000276Z

Hmm, yeah, I agree

niwinz 2017-01-02T10:59:28.000277Z

I need to think about it...

niwinz 2017-01-02T11:06:52.000279Z

@martinklepsch thanks for the feedback and your thoughts!

martinklepsch 2017-01-02T11:18:59.000280Z

@niwinz you’re most welcome haha

martinklepsch 2017-01-02T11:19:19.000281Z

Enjoying how much simpler Potok makes async stuff

🎉 2
martinklepsch 2017-01-02T12:03:45.000282Z

So, decided to swap my own wonky xhrio thing for httpurr, now I’m struggling to understand how to properly put it into the rx machinery

(->> (rx/from-promise
           (http/send! xhr/client
                       {:url "/get-friends"
                        :method :post
                        :body (-&gt;&gt; {:url "<https://api.twitter.com/1.1/friends/list.json>"
                                    :method :GET
                                    :user-params {}}
                                   (merge (get-in @app-db [:current-user :credentials :twitter]))
                                   (clj-&gt;js)
                                   (gjson/serialize))
                        :headers {"Content-Type" "application/json;charset=UTF-8"}}))
          (rx/delay 2000)
          (rx/map (fn [data]
                    (js/console.log data)
                    (-&gt;UiDone (hash this))))))))
This is what I have… I would have expected the error to be raised somewhere if theres a 4** but instead it’s just logged in the rx/map step. Is that expected?

martinklepsch 2017-01-02T12:07:53.000283Z

@niwinz ^ any suggestions? 🙂

2017-01-02T12:12:23.000284Z

hey @martinklepsch, in httpurr the responses that are 4xx and 5xx are not treated as errors (the server responded); the connection errors or timeouts are

martinklepsch 2017-01-02T12:13:32.000285Z

Oh! that makes perfect sense and sounds like a very reasonable decision 👍

martinklepsch 2017-01-02T12:13:51.000286Z

somehow missed that reading the docs/assumed it would be like clj-http et al

2017-01-02T12:13:57.000287Z

can be a bit surprising but i think makes sense

2017-01-02T12:14:22.000289Z

you can discern reponse types and convert to a rejected promise in the then handler

martinklepsch 2017-01-02T12:14:49.000290Z

Agree, I very often use {:throw-exceptions? false} when using clj-http

martinklepsch 2017-01-02T12:17:53.000291Z

@dialelo in the example above I would use rx/map to check the status code and then rx/throw if I wanted an error to occur?

2017-01-02T12:19:25.000292Z

i'm not entirely sure about the rx API but I think that will work

2017-01-02T12:21:25.000293Z

alternatively you can do it with promise's then, something like (prom/then request (fn [response] (if (status/error? response) (prom/rejected "An error happened!") (parse-body response)))

niwinz 2017-01-02T12:28:15.000294Z

alternatively, you can use this thin http client https://github.com/uxbox/uxbox/blob/master/frontend/src/uxbox/util/http.cljs

niwinz 2017-01-02T12:28:27.000296Z

it is based on httpurr but uses rx streams

niwinz 2017-01-02T12:28:55.000297Z

on future I have plans to export it as a library with httpurr similar api

martinklepsch 2017-01-02T12:36:29.000298Z

@niwinz that’s a good pointer, thanks

martinklepsch 2017-01-02T12:36:40.000299Z

@dialelo using rx/throw worked like a charm

martinklepsch 2017-01-02T12:37:31.000300Z

@niwinz one benefit of using a registry (and not vars) is that you don’t to require a namespace with the var you need and instead can handle that in one spot

martinklepsch 2017-01-02T12:38:00.000301Z

@niwinz that makes it a lot easier to move event definitions around as well

niwinz 2017-01-02T12:38:23.000302Z

yeah, I know that it has benefits.

niwinz 2017-01-02T12:38:42.000303Z

I need to think in a way of have the best of both

niwinz 2017-01-02T12:39:16.000304Z

have the rx stuf from potok and have the ability to split impl and event construction

martinklepsch 2017-01-02T12:39:34.000305Z

oh and one thing: the tip you gave earlier… using (partial emit! …) with some logging on top — it doesn’t support logging of events that are the result of WatchEvents

niwinz 2017-01-02T12:40:14.000306Z

instrumentation and logging support should be added...

niwinz 2017-01-02T12:40:21.000307Z

partial emit!... is just a workaround

niwinz 2017-01-02T12:43:40.000308Z

> "one benefit of using a registry (and not vars) is that you don’t to require a namespace with the var you need and instead can handle that in one spot" hmm, but in any case you will need to import ns where events are defined? I'm missing something?

niwinz 2017-01-02T12:44:03.000309Z

or you are thinking in events defined as plain data?

martinklepsch 2017-01-02T12:46:28.000310Z

> hmm, but in any case you will need to import ns where events are defined? I'm missing something? Yes you still need to import them but through the registry indirection you could have a mapping of {keyword event} and only provide to the keyword whenever you want to refer to the event

martinklepsch 2017-01-02T12:47:35.000311Z

@niwinz if you end up thinking about logging I think it might also be nice to consider interceptors or a similar approach. I haven’t needed them a lot when using re-frame but they certainly seem very extendable and can easily solve issues like logging

niwinz 2017-01-02T12:48:17.000313Z

ok thanks

niwinz 2017-01-02T12:48:27.000314Z

i need to re-read the reframe doc

martinklepsch 2017-01-02T12:48:39.000315Z

I hope what I’m saying makes sense

martinklepsch 2017-01-02T12:51:27.000316Z

I’ll try to experiment a bit as well

niwinz 2017-01-02T12:52:58.000317Z

😄

niwinz 2017-01-02T15:48:00.000322Z

that is almost correct

niwinz 2017-01-02T15:48:24.000323Z

just return (rx/of (-&gt;Firebase...) (-&gt;UiDone (has...))

niwinz 2017-01-02T15:48:51.000324Z

or (rx/from-coll vector-of-events-here)

martinklepsch 2017-01-02T16:09:53.000325Z

@niwinz not sure I understand, I need to do something with the result of the FirebasePush event, rx/of has the same issue like rx/just, which is that data-snapshot in the above snippet is just the FirebasePush record

martinklepsch 2017-01-02T16:10:06.000326Z

I’m almost definitely approaching things from the wrong angle haha

niwinz 2017-01-02T16:10:34.000327Z

hmm, seems that I have explained me badly 😛

niwinz 2017-01-02T16:10:45.000328Z

the return value of mapcat should be a stream of events

niwinz 2017-01-02T16:10:54.000329Z

not a vector of events

niwinz 2017-01-02T16:11:21.000330Z

in your example code snippet you are returning a vector of events on mapcat

niwinz 2017-01-02T16:12:02.000331Z

and in order to fix it, just use rx/of passing each event as positional argument or call rx/from-coll with the same vector that you are returning

niwinz 2017-01-02T16:12:53.000332Z

(-&gt;&gt; (rx/just (-&gt;FirebasePush type (get-in state (into [::forms] form-path))))                      
     (rx/delay 1000)                                                                                 
     (rx/mapcat (fn [data-snapshot]                                                                 
                  (js/console.log :data-snapshot data-snapshot)                                     
                  (rx/of (-&gt;FirebaseDomainObject type (.-key data-snapshot) (to-clj (.val data-snapshot)))
                         (-&gt;UiDone (hash this))]))))))    

martinklepsch 2017-01-02T16:12:54.000333Z

I’ve been using mapcat like that the whole time 😄

martinklepsch 2017-01-02T16:13:12.000335Z

let me try

martinklepsch 2017-01-02T16:14:37.000336Z

So in your snippet data-snapshot will still be the FirebasePush record and not the result of that event

niwinz 2017-01-02T16:15:08.000337Z

ofcourse

niwinz 2017-01-02T16:15:41.000339Z

If you want to syncronize events, you should do something different

martinklepsch 2017-01-02T16:15:43.000340Z

I think my understanding is… lacking a bit 😄

niwinz 2017-01-02T16:16:51.000341Z

just wait 5 min, I'll try to explain how it can be done with an example

martinklepsch 2017-01-02T16:20:15.000342Z

@niwinz that’s very kind, thanks in advance!

niwinz 2017-01-02T16:28:55.000344Z

this is a pretty complex example synchronizing asynchronous flow

niwinz 2017-01-02T16:32:12.000346Z

many function calls such as http/ and router/ are just examples

niwinz 2017-01-02T16:42:26.000347Z

Additionally, one thing that is pretty cool in potok, is that you can emit anything you want, it is not mandatory to implement any event protocols

niwinz 2017-01-02T16:42:32.000348Z

a good example are keywords

niwinz 2017-01-02T16:42:57.000349Z

they serves pretty well for synchronize async flows in some ocasions

niwinz 2017-01-02T16:43:01.000350Z

there are a example:

niwinz 2017-01-02T16:43:44.000352Z

The flow is, EventA --> EventB --> EventC

niwinz 2017-01-02T16:44:10.000353Z

EventC will be emited after EventB have finished

niwinz 2017-01-02T16:44:22.000354Z

and EventB finishes emiting a keyword to the stream

niwinz 2017-01-02T16:45:29.000356Z

i hope you find this useful @martinklepsch

martinklepsch 2017-01-02T16:47:15.000357Z

Thanks for the detailed example @niwinz

martinklepsch 2017-01-02T16:47:55.000358Z

after reading the potok docs earlier I remembered that this stream argument to watch events was good for something

martinklepsch 2017-01-02T16:47:57.000359Z

😄