-
-
Notifications
You must be signed in to change notification settings - Fork 28
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
Add HTTP/websocket event sources #49
Comments
You can wrap Ajax requests this way: import org.scalajs.dom.ext.Ajax
EventStream.fromFuture(Ajax.get(url)) |
Then maybe there should be something to integrate things lazily but on a more general level. |
I think having a reference implementation of doing basic stuff like Ajax would be useful. There are three potential places for it:
Stuff that we add to Airstream should be pretty unopinionated. For example, I've been wanting to move A few notes though:
Currently one can achieve this with something like: def fromLazyFuture[A](future: => Future[A], emitOnce: Boolean): EventStream[A] = {
EventStream
.fromValue((), emitOnce =emitOnce)
.flatMap(_ => future)
} I guess I could add this EventStream and a similar one to Signal. |
Oh and, I didn't have the time to evaluate the websockets helper yet. I think websockets might require a more opinionated API, but I'll check it out a bit later. |
Thanks for your suggestions. I will incorporate them and submit a PR. Regarding websockets,
|
Ok wrt websockets! Try to keep it unopinionated so that users can build their custom code based on it, like you did with ajax. regarding "restart on error" – I'm guessing regarding retrys – what kind of operators / signatures are you thinking about, specifically? Kinda hard to translate ZIO stuff directly as it has many more concepts than Airstream. We have |
The usecase I had in mind is to restart a websocket stream when the underlying connection stops, for instance, when the user loses connectivity. When connectivity is resumed, it may be desirable to establish a new websocket connection automatically. One way to do this is to use a control parameter like |
Yes, sorry, I worded it poorly, I understand the use cases for retrys, but I was also wondering the same thing, whether you had a specific method signature and behaviour in mind that would be general enough to include as an operator for all streams. I'm thinking it could be a certain variation of |
How about |
But, this would only work for streams that perform a side effect (that we want to re-run) when they start. Most streams don't do that. Even if you're listening to a stream that depends on one of those streams that perform a side effect on start, and you stop & start that child stream, that is no guarantee that the parent stream that actually performs the side effect will be restarted (that will only happen if the child stream was its only listener). I think the problem is, streams aren't effects. You can retry an effect like IO or Task or even Future, but retrying a stream doesn't seem to make sense in general. Or, to the extent that it does, the retry logic needs to be contained within the stream that actually performs the side effect. So if there is any reusability to be gained there, it does not seem like it will be in the form of a general purpose operator. |
The initial draft for the websocket implementation is available in this branch..
Need some input on the following:
I will test this implementation in my application and explore the restart-on-error usecase. |
The above issues have been resolved for now.
Added
Redefined error type to support transmission and termination errors.
These are not propagated. TODO |
As I'm adding more params to AjaxStreamRequest, I'm getting more and more annoyed by having to duplicate all them in the six factory methods (apply / get / post / etc.). How about we just have one // Usage
AjaxEventStream(_.GET, url, ...) where the first param would be @js.native
trait HttpMethod extends js.Any
object HttpMethod {
val GET = "GET".asInstanceOf[HttpMethod]
val POST = "POST".asInstanceOf[HttpMethod]
val PUT = "PUT".asInstanceOf[HttpMethod]
val PATCH = "PATCH".asInstanceOf[HttpMethod]
val DELETE = "DELETE".asInstanceOf[HttpMethod]
} |
How about something similar to this pattern? object AjaxEventStream {
def apply(
url: String,
data: dom.ext.Ajax.InputData = null,
timeout: Int = 0,
headers: Map[String, String] = Map.empty,
withCredentials: Boolean = false,
responseType: String = "",
progressObserver: Observer[(dom.XMLHttpRequest, dom.ProgressEvent)] = Observer.empty,
readyStateChangeObserver: Observer[dom.XMLHttpRequest] = Observer.empty
) = new Builder(url, data, timeout, headers, withCredentials, responseType, progressObserver, readyStateChangeObserver)
final class Builder(
url: String,
data: dom.ext.Ajax.InputData,
timeout: Int,
headers: Map[String, String],
withCredentials: Boolean,
responseType: String,
progressObserver: Observer[(dom.XMLHttpRequest, dom.ProgressEvent)],
readyStateChangeObserver: Observer[dom.XMLHttpRequest]
) {
def get: EventStream[dom.XMLHttpRequest] = ???
def post: EventStream[dom.XMLHttpRequest] = ???
// and so on...
}
} |
What would be in those |
I guess AjaxEventStream could accept (This is as concerning Ajax, I haven't had a change to review Websockets stuff yet. I should probably look at that first so that the API ends up more or less consistent if that makes sense) |
Correct. I prefer the usage pattern it promotes. AjaxEventStream(url).get
// versus
AjaxEventStream(_.GET, url, ...))
An alternate encoding could be object AjaxEventStream {
sealed abstract class method(name: String) {
final def apply(
url: String,
data: dom.ext.Ajax.InputData = null,
timeout: Int = 0,
headers: Map[String, String] = Map.empty,
withCredentials: Boolean = false,
responseType: String = "",
progressObserver: Observer[(dom.XMLHttpRequest, dom.ProgressEvent)] = Observer.empty,
readyStateChangeObserver: Observer[dom.XMLHttpRequest] = Observer.empty): EventStream[dom.XMLHttpRequest] =
new AjaxEventStream(name, url, data, timeout, headers, withCredentials, responseType, progressObserver, readyStateChangeObserver)
}
final object get extends method("GET")
final object post extends method("POST")
// and so on
}
// usage
AjaxEventStream.get(???) |
Hm that last one is pretty neat. If it works ok with editor autocomplete (I'll check intellij), that's a winner. Side note, |
Thanks. I had extended this best-practice to non-case objects and followed it blindly. Time to self correct! |
I am currently using these as HTTP event sources.
Would it make sense to add these to the library?
The text was updated successfully, but these errors were encountered: