Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an API for event listeners #1

Closed
RLesur opened this issue Nov 19, 2018 · 6 comments
Closed

Implement an API for event listeners #1

RLesur opened this issue Nov 19, 2018 · 6 comments

Comments

@RLesur
Copy link
Owner

RLesur commented Nov 19, 2018

Implementing event listeners is not so easy.

Assume that we want to navigate on a website and ensure that the frame is loaded. Since multiple frames can be opened, we want to ensure that the main frame is loaded.
In order to achieve this task, we have to send Page.navigate, retrieve the frameId of the response and register a listener on the event Page.frameStoppedLoading for the given frameId.

With the current version of crrri, we have to write this (ugly) code:

library(crrri)
library(promises)
con <- chr_connect()

page <- con %>% Page.enable() # active the events

google_loaded <- page %>%
  Page.navigate(url="http://google.fr") %>% 
  then(onFulfilled = function(value) {promise(function(resolve, reject) {
    ws <- value$cnx$ws
    ws$onMessage(function(event) {
      data <- jsonlite::fromJSON(event$data)
      if (!is.null(data$method) & !is.null(data$params$frameId))
        if (data$method == "Page.frameStoppedLoading" & data$params$frameId == value$result$frameId)
          resolve(list(cnx = value$cnx, result = data$params))
    })
  })})

So, we need to implement high level functions in order to register callbacks on events.
For instance, we could do that:

Page.navigate(url="htttp://google.fr") %>%
  Page.frameStoppedLoading(frameId = ~frameId) # or frameId = ~ result$frameId

I wonder what would be the best API?

@cderv
Copy link
Collaborator

cderv commented Nov 19, 2018

I need to dig a little onto how all this works really but with only what I currently know and from what you describe I am thinking of something like monitorEvents.

Page.navigate(url="htttp://google.fr") %>%
    monitor(Page.frameStoppedLoading, frameId = .res$frameId)

like a condition to meet. (or Listen() for listening to listeners ?)

However, your approach is also good. I prefer something like

Page.navigate(url="htttp://google.fr") %>%
  Page.frameStoppedLoading(frameId = ~ .res$frameId)

where .res is the results from before like .x in purrr.

Does it make sense ?
I think, I need to better understand listeners to think clearly about this.

@RLesur
Copy link
Owner Author

RLesur commented Nov 20, 2018

Events listeners are useful for two different goals:

  1. Waiting that the event fires to proceed the remaining tasks. I did some tests, and it should work well with pipes and an API like this:
    Page.navigate(url="http://google.fr") %>% 
      Page.frameStoppedLoading(frameId = ~ .res$frameId) %>%
      remaining_task()
  2. Register callback functions. I think this usage should be rare but when I see the Page.screencastFrame event, the user would need to register a callback function to save each frame on the disk.

There's a huge difference between awaiting that one event fires once and calling back a function each time the event fires.
For the second usage (callback registering), I think it would be more intuitive to have a function like onEvent('Page.frameStoppedLoading', callback = f) but we also could do the trick with an extra argument (for instance .callback)

Page.frameStoppedLoading(.callback = f)

I see a huge advantage to have R functions like Page.frameStoppedLoading() for events handling: we can build the documentation and have auto completion in RStudio (all the names are awful). Writing the full name of events would be prone to error.

@cderv
Copy link
Collaborator

cderv commented Nov 20, 2018

The extra argument is really interesting. Same function for both case. I like
Page.frameStoppedLoading(.callback = f)

About autocompletion, we could leverage NSE and use onEvent(Page.frameStoppedLoading, callback = f) function name as symbol in first argument. I think the autocompletion should work. How would it work in a pipe ?

Otherwise, it would be a big change, but a R6 logic would help in those case

Page$frameStoppedLoading$onEvent(callback = f)

The whole API could be written as low level with OOP approach - don't know if it worth keeping it in mind as we loose also a lot.

@RLesur
Copy link
Owner Author

RLesur commented Nov 20, 2018

I wonder many times too for OOP: it would be much more secure but it is not clear for me how to combine R6 with promises and the websocket connexion. I see one advantage of this first draft (without OOP): we are close to the metal and how the DevTools protocol is used in Javascript.

@cderv
Copy link
Collaborator

cderv commented Nov 20, 2018

we are close to the metal and how the DevTools protocol is used in Javascript.

Yes absolutely! I think it is also easier to generate the code and the documentation using FP. Will see how it will do on the run. I'll open an issue for reference that this was a question. We'll close when we are sure.

RLesur pushed a commit that referenced this issue Nov 22, 2018
@RLesur
Copy link
Owner Author

RLesur commented Nov 23, 2018

I think it's done.

@RLesur RLesur closed this as completed Nov 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants