Fork me on GitHub
#funcool
<
2017-01-02
>
martinklepsch09:01:06

@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?

niwinz09:01:32

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

niwinz09:01:25

In uxbox I have get rid of promises

niwinz09:01:41

http and everything async related is now usig rx

martinklepsch09:01:30

don’t like promises anymore? 😄

niwinz09:01:07

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

niwinz09:01:40

they enables better resource management...

niwinz09:01:38

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

niwinz09:01:59

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

martinklepsch09:01:32

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

martinklepsch09:01:00

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

niwinz09:01:17

not all api is documented with examples

martinklepsch09:01:56

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

martinklepsch10:01:20

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? 😄

martinklepsch10:01:58

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

martinklepsch10:01:43

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?

niwinz10:01:13

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

niwinz10:01:52

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

niwinz10:01:44

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

niwinz10:01:46

over value

martinklepsch10:01:54

So for stubbing out an event you just redef it?

niwinz10:01:01

and return a instance of event that transforms the state

martinklepsch10:01:19

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

niwinz10:01:18

That looks good if you need that

niwinz10:01:04

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

niwinz10:01:02

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

martinklepsch10:01:15

> 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

niwinz10:01:46

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

martinklepsch10:01:47

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

niwinz10:01:29

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

(ptk/emit! store increment)

martinklepsch10:01:05

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

niwinz10:01:39

hmm strange

niwinz10:01:10

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

martinklepsch10:01:54

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)))

niwinz10:01:05

😞 maybe is a bug in concat

martinklepsch10:01:08

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

niwinz10:01:47

on the contrary, you are helping!

martinklepsch10:01:01

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

niwinz10:01:55

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

niwinz10:01:47

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

niwinz10:01:05

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

niwinz10:01:42

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

martinklepsch10:01:01

yeah, that seems to be a sensible approach

martinklepsch10:01:40

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

niwinz10:01:21

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

martinklepsch10:01:33

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

niwinz10:01:06

do you know specify! macro?

martinklepsch10:01:18

heh, that’s what I was just thinking about

niwinz10:01:14

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

niwinz10:01:38

if I want to mock some event that is emited indirectly

martinklepsch10:01:47

I guess I could implement a registry that does

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

niwinz10:01:55

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

martinklepsch10:01:58

not specify! infact but kind of similar

niwinz10:01:40

yeah, you can implement a own registry like stuff

martinklepsch10:01:11

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

niwinz10:01:23

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

martinklepsch10:01:09

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

niwinz10:01:58

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

niwinz10:01:06

the implementation are in one file

niwinz10:01:16

and events and event constructors are in other

niwinz10:01:55

wen you are working with complex flows,

niwinz10:01:56

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

niwinz10:01:45

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

niwinz10:01:13

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

niwinz10:01:41

is my opinion

martinklepsch10:01:22

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

niwinz10:01:21

Hmm, yeah, I agree

niwinz10:01:28

I need to think about it...

niwinz11:01:52

@martinklepsch thanks for the feedback and your thoughts!

martinklepsch11:01:59

@niwinz you’re most welcome haha

martinklepsch11:01:19

Enjoying how much simpler Potok makes async stuff

martinklepsch12:01:45

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 (->> {:url ""
                                    :method :GET
                                    :user-params {}}
                                   (merge (get-in @app-db [:current-user :credentials :twitter]))
                                   (clj->js)
                                   (gjson/serialize))
                        :headers {"Content-Type" "application/json;charset=UTF-8"}}))
          (rx/delay 2000)
          (rx/map (fn [data]
                    (js/console.log data)
                    (->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?

martinklepsch12:01:53

@niwinz ^ any suggestions? 🙂

dialelo12:01:23

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

martinklepsch12:01:32

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

martinklepsch12:01:51

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

dialelo12:01:57

can be a bit surprising but i think makes sense

dialelo12:01:22

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

martinklepsch12:01:49

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

martinklepsch12:01:53

@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?

dialelo12:01:25

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

dialelo12:01:25

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)))

niwinz12:01:27

it is based on httpurr but uses rx streams

niwinz12:01:55

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

martinklepsch12:01:29

@niwinz that’s a good pointer, thanks

martinklepsch12:01:40

@dialelo using rx/throw worked like a charm

martinklepsch12:01:31

@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

martinklepsch12:01:00

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

niwinz12:01:23

yeah, I know that it has benefits.

niwinz12:01:42

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

niwinz12:01:16

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

martinklepsch12:01:34

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

niwinz12:01:14

instrumentation and logging support should be added...

niwinz12:01:21

partial emit!... is just a workaround

niwinz12:01:40

> "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?

niwinz12:01:03

or you are thinking in events defined as plain data?

martinklepsch12:01:28

> 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

martinklepsch12:01:35

@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

niwinz12:01:27

i need to re-read the reframe doc

martinklepsch12:01:39

I hope what I’m saying makes sense

martinklepsch12:01:27

I’ll try to experiment a bit as well

niwinz15:01:00

that is almost correct

niwinz15:01:24

just return (rx/of (->Firebase...) (->UiDone (has...))

niwinz15:01:51

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

martinklepsch16:01:53

@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

martinklepsch16:01:06

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

niwinz16:01:34

hmm, seems that I have explained me badly 😛

niwinz16:01:45

the return value of mapcat should be a stream of events

niwinz16:01:54

not a vector of events

niwinz16:01:21

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

niwinz16:01:02

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

niwinz16:01:53

(->> (rx/just (->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 (->FirebaseDomainObject type (.-key data-snapshot) (to-clj (.val data-snapshot)))
                         (->UiDone (hash this))]))))))    

martinklepsch16:01:54

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

martinklepsch16:01:37

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

niwinz16:01:41

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

martinklepsch16:01:43

I think my understanding is… lacking a bit 😄

niwinz16:01:51

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

martinklepsch16:01:15

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

niwinz16:01:55

this is a pretty complex example synchronizing asynchronous flow

niwinz16:01:12

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

niwinz16:01:26

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

niwinz16:01:32

a good example are keywords

niwinz16:01:57

they serves pretty well for synchronize async flows in some ocasions

niwinz16:01:01

there are a example:

niwinz16:01:44

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

niwinz16:01:10

EventC will be emited after EventB have finished

niwinz16:01:22

and EventB finishes emiting a keyword to the stream

niwinz16:01:29

i hope you find this useful @martinklepsch

martinklepsch16:01:15

Thanks for the detailed example @niwinz

martinklepsch16:01:55

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