-
Notifications
You must be signed in to change notification settings - Fork 38
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
Events / Observables #71
Comments
How does a user subscribe to those events? |
In common usecases I've seen: either MDN's addEventListener or EventEmitter's: In the former case, I can see specifying an |
In JSDoc the events emitted are enumerated with @fires and the events are described by @event:
Perhaps another configuration could be:
My only concern here is multiple objects could share events with the same name. For instance, |
I like |
So, 3 implementations come to mind: 1. Implement
The drawback there is that it's not guaranteed or implied that any of, or which of, the extended classes is the event emitter. 2. emits from
This is basically a special invocation of 3. Be opinionated We could take the opinion that any interface should only contain a single interface for events and create a special
|
No. That's not always what's happening. See the "composing types" PR. |
Ah, excellent. So the spread operator might be useful here, too. The first implementation would look more like:
Alternatively, the spread operator could be overloaded in the case of the emits clause for more clarity:
|
This form looks correct to me... interface ChannelInstance {
...EventEmitter,
displayName: String
}, emits: {
messageAdded: { body: String, authorId: Number },
memberJoined: { id: Number }
} |
Since we are doing documentation, we will need to have some way to add the model boolean. Of course that only matters for a DOM-like environment, which is pretty common.
|
I'm not sure we need to do that. Seems like something that should be called out in prose and explained in more depth, anyway...? |
That was just an idea I was throwing. Maybe it's going too far. |
Can’t we achieve the same in the current spec without adding features (read: complexity)? interface ChannelInstance {
...EventEmitter,
displayName: String,
on: ('message added', { body: String, authorId: Number }) => Void,
on: ('member joined', { id: Number }) => Void,
} By the way, an event emitter will differ in different contexts. A custom element will emit DOM events, whereas a node server will likely use EventEmitter – and both ways are idiomatic. It’d be wrong to tie ourselves to one context. |
on: ('message added', { body: String, authorId: Number }) => Void,
on: ('member joined', { id: Number }) => Void, This is not currently supported, and I find the syntax a bit ambiguous. How do you tell it apart from a normal function signature? |
I meant it to be a normal overloaded function signature. I’m not an expert with our syntax yet though – chances are it should have been something like: on: {
('message added', { body: String, authorId: Number }) => Void,
('member joined', { id: Number }) => Void,
}, |
That makes a lot more sense, and yes, that would work fine, except that different implementations might have different subscribe methods, so if we rely strictly on this, it doesn't necessarily scream "this is an event emitter". |
The point I’m trying to make is: Do we really need to scream “this is an event emitter”, since there are so many valid tastes of event emitters and since our current syntax covers all of them already? |
I suppose that's a good question. |
Looking to clarify re: on: {
('message added', { body: String, authorId: Number }) => Void,
('member joined', { id: Number }) => Void,
}, Is |
The latter, though I don't see why it would be necessary to redefine each event for each corresponding method... |
I understand the desire to re-use existing functionality, and I find overloading by event name and handler signature creative and efficient. But I have concerns:
I guess it comes down to whether we consider the observable pattern to be important enough to facilitate explicit documentation. I do.
I suppose it wouldn't, but then (unless I'm missing something) we're defining one method with all of these overloads, and hoping it's somehow implied that any other given method is or is not related to listening to those events. |
How do you have to repeat yourself? Can you show me an example?
They're not method overrides. It's the function polymorphism syntax, slightly abused to create separate signatures for each of the event-type values. ;)
This is one of my concerns, which is why I thought an explicit
This happens anyway with the |
Well, I guess I wouldn't have to but I don't think the alternative is much better:
or
(Not quite sure how
I see that both approaches have a similar problem. With I think either approach can be modified to be appropriate:
For
For the alternative, perhaps something like this would be enough:
Although the latter would probably result in duplicate documentation of the same events for each method name, and I guess they're still not explicitly event related. |
If you use the object spread syntax to mix in the emitter's natural API, you don't even have to do that. interface Thing {
...EventEmitter,
on: {
('message added', { body: String, authorId: Number }) => Void,
('member joined', { id: Number }) => Void,
}
} |
You're right. We would just need to figure out how to denote that |
We need a function signature example.
Is this correct? |
@Mouvedia Although the function may trigger an event, the event is actually emitted from the class. |
@ryan-rowland sorry could you be more precise? What are you calling a "class"? |
@Mouvedia Can you give more context to your example code snippet? |
OK I see what you mean now: the event can't be simulated so it's not actionnable. Is it made for things like the Push API? |
I think it's important we remain consistent with the IMO, we don't need to explicitly spell out the event API -- just mix in the event emitter with the object spread syntax. I propose: interface MyEventBus {
...EventEmitter
}, emits: {
('message added', { body: String, authorId: Number }),
('member joined', { id: Number })
} Note: I think it's obvious that |
Shouldn't this part
be
instead? |
Maybe. What does everybody think of that? I think it could be confusing, because some event emitters support any number of arguments. I've always thought of it as a function call (because it translates into a callback function call)... so I see it as a function signature for the callback, minus the implied (eventType: String, arg1: Type, arg2: Type, ...rest: Type) |
Do you have a non-node.js example? |
Example from Backbone docs: interface Alerter: {}, emits: {
('alert', msg: String)
} var object = {};
_.extend(object, Backbone.Events);
object.on("alert", function(msg) {
alert("Triggered " + msg);
});
object.trigger("alert", "an event"); jQuery events, custom DOM events, any universal EventEmitter usage are all commonly used in browsers. |
Is there a |
yes. This is why we need a working parser. |
My 2 cents: emits: {
('message added', { body: String, authorId: Number }),
('member joined', { id: Number })
} This seems like an odd hybrid to me. The definitions seem to be describing overloaded arguments for the emits: {
'message added': { body: String, authorId: Number },
'member joined': { id: Number }
} I prefer the above because the intention is a littler clearer. We're saying "Here's a map of what this class emits, to the arguments passed to its listeners" in a way that's truer to JSON standards. |
Remember, there can be any number of arguments emitted with an event. We literally are describing a function signature when we describe events, because they are always triggered via callback (at least, they are in every popular implementation currently in use). This is what the spec needs to capture: (eventName: String, arg1: Type, arg2: Type, ...rest: Type) In other words, this doesn't work because it's ambiguous: emits: {
'message added': { body: String, authorId: Number },
'member joined': { id: Number }
} Could mean: emits: {
// one event
('message added': { body: String, authorId: Number }, 'member joined': { id: Number })
} I don't think that's what you meant by your example, but how can a parser tell the difference? |
Ah. I see where we're missing each other. I've been using the curly brackets to denote multiple parameters, not a single object.
Was meant to represent
So perhaps clearer notation would be:
|
This is acceptable to me if everybody else likes it better: emits: {
'messageAdded': (body: String, authorId: Number),
'memberJoined': (id: Number, { Name: String, email: String })
} I guess that makes sense if you think of it this way:
This fits our existing method syntax: emits: {
messageAdded(body: String, authorId: Number),
memberJoined(id: Number, { Name: String, email: String })
} I'm not sure if we've documented it, but IMO, it should be perfectly legal to use quotes and colons with the method syntax, too: interface MyCallbacks {
'messageAdded': (body: String, authorId: Number),
'memberJoined': (id: Number, { Name: String, email: String })
} |
@ericelliott Pretty sure I'm +1 to: emits: {
'messageAdded': (body: String, authorId: Number),
'memberJoined': (id: Number, { Name: String, email: String })
} To clarify, I'm interpreting this
Is that right? |
@ryan-rowland Looks like you got it. @Mouvedia I don't understand what the |
@ericelliott function expression vs function declaration
|
@Mouvedia I believe those forms should be considered interchangeable. AFAIK, there is no difference in the type produced by function expressions vs declarations in JS object definitions. |
Late to the party, but I don’t really like where this went in the end. If I have an API like interface MyEmitter {}, emits: {
'messageAdded': (body: String, authorId: Number),
'memberJoined': (id: Number, { Name: String, email: String })
} how do I consume it? myEmitter.on('messageAdded', (…) => {…});
myEmitter.addEventListener('messageAdded', (…) => {…});
myEmitter.messageAdded.subscribe((…) => {…}); All of these are valid ways to emit events in different environments: node / DOM / RxJS. And I’m sure there are more ways. |
Besides, if we keep adding new features to the spec instead of looking for solutions which cover more cases (even if it means a character or two more to type, as in the overloading example), we’ll end up with scope creep. The fatter the spec gets, the harder it’ll be for others to learn – and the less likely it is to adopt widely. In the end, what spoke against using overloading for describing event emitters? |
@tomekwi One of the most important goals for me was to create a standard for documenting APIs that we can use in technical documentation. We needed a standard, easily machine-readable way to document events, regardless of the listening API. As for scope creep, I don't expect early implementations to cover all the features, but event documentation was a must-have feature for me that needed to be in the MVP version. I'm already using it. |
Should we add an example featuring a constructor as well? |
I'd be interested in discussing how expected events on observable objects could be described. Here's a first draft for one possibility based on my understanding of the current structural types:
Thoughts?
The text was updated successfully, but these errors were encountered: