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
Enable .then
with event dispatchers
#6915
Comments
.then
with event dispatchers
At first glance, I like the idea as it provides more flexibility. Being able to find out when event handlers have finished running has parallels with the DOM event model: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent says that "All applicable event handlers are called and return before I do have a couple comments on your proposed implementation. The first is that you don't have a return value if the And second, while I appreciate the fact that you prefer to build up arrays in immutable-data fashion using Overall, though, I like this idea. The only drawback I can think of is that people might be tempted to use |
@rmunn , thanks for the feedback, you made two excellent points. function createEventDispatcher() {
const component = get_current_component();
return (type, detail) => {
const callbacks = component.$$.callbacks[type];
if (callbacks) {
let arr = []
const event = custom_event(type, detail);
callbacks.slice().forEach((fn) => {
const res = fn.call(component, event);
if (res instanceof Promise) arr.push(res)
});
return Promise.all(arr).then(() => true)
}
};
} I realized that I don't need to push all the the callbacks into the array of Promises, as the synchronous ones (if I am not wrong) will be executed before the end of each iteration, so I don't have to artificially wait for them by wrapping them into a Promises (same applies to
|
Looks good. You still need an |
Oh yes, I understand now. This raises another question though, should this second function createEventDispatcher() {
const component = get_current_component();
return (type, detail) => {
const callbacks = component.$$.callbacks[type];
if (callbacks) {
let arr = []
const event = custom_event(type, detail);
callbacks.slice().forEach((fn) => {
const res = fn.call(component, event);
if (res instanceof Promise) arr.push(res)
});
return Promise.all(arr).then(() => true)
}
return new Promise((resolve) => resolve(false))
};
} |
Well, currently there's no equivalent to event cancellation for Svelte-dispatched events, so there are no circumstances in which you'd need to return if (callbacks) {
let arr = []
const hasCallbacks = !!callbacks.length
const event = custom_event(type, detail);
callbacks.slice().forEach((fn) => {
const res = fn.call(component, event);
if (res instanceof Promise) arr.push(res)
});
return Promise.all(arr).then(() => hasCallbacks)
}
return new Promise((resolve) => resolve(false)) Then the result would be |
I agree with you, the latter makes most sense. |
This is a sweet idea! I tried implementing it in a project which was forwarding my custom events and it breaks, unfortunately. I'm not smart enough to figure out why at the moment, but here's a REPL, and potentially a place to find answers? |
@fartinmartin thx! |
@AyloSrd incredible! Excited to see your PR! 😊 |
I'm not really sure this is the right feature to add. Event dispatchers (and the event model in general) is sync only, similar to DOM events, once dispatched it doesn't wait for any promises and returns immediately. For example, const a = document.getElementById('anchor_tag')
a.addEventListener('click', async (e) => {
await doSomething()
e.preventDefault() // stop browser from opening the link (does not work)
}) The code above doesn't work as While Svelte's internal implementation allows bypassing this caveat, I think it's important for Svelte to map the DOM's limitation to prevent gotchas for end-users down the road. |
Hi @bluwy thx for your feedback, it's a valid point. Please see the reasoning behind my feature request, that focuses on two points: 1. The aforementioned parallel with the DOM: even if, as you rightly point out, DOM dispatchers are synchronous, they still return after all the handlers have returned, thus letting know when the latter have run, as pointed out by @rmunn here. Also event dispatchers return a boolean, depending on whether at least one of the handlers has called 2. The Component-based nature of Svelte: ideally a component should be able to behave almost like a native element, in the sense that the parent element should treat it as it would a native one, being agnostic of it's children's internal working. Likewise, children elements should be conceived independently from their potential parents. Knowing when synchronous and asynchronous handlers have returned/resolved will, IMO, help make components much more independent, giving a lot more flexibility to the developer and improving dev-xp by removing the need of passing additional props. This Component-nature reason would justify taking this slight detour from classical DOM dispatchers. Furthermore, Svelte's dispatcher has already some important differences with the DOM's one, e.g. allowing passing a payload, while in vanilla JS it should be passed during the event initialisation (this is because Svelte's dispatch also create the custom event). Therefore I don't believe that adding this feature would negative impact or shock the end user, as it has already been made clear that the two dispatch are not perfectly aligned. I hope my points are clear enough. To be more faithful to DOM behaviour, I could avoid waiting for the handlers to resolve, and only return true when they return, or false when the handler is |
It does return a boolean since 3.48.0 (#7064), aligning with the DOM
I believe this is the main concern, we shouldn't be adding features on top of the reflecting DOM API, and instead try to replicate it as close as possible.
While I think this is true, deviating from the DOM API isn't the right solution. It would probably take more boilerplate to support your usecase, but I think it's more correct so we don't break standards.
I don't see how Svelte's is different than the DOM. For me: dispatch('my-event', { pay: 'load' }, { cancelable: true }) maps to el.dispatchEvent(new CustomEvent('my-event', { detail: { pay: 'load' }, cancelable: true }) Svelte's simply providing sugar to dispatch events within its own restrictions (components), and it doesn't implement anything non-standard. Perhaps you can elaborate more on something that I've missed? |
Oh, I completely missed this part! My bad. |
No problem. I should've brought up that PR earlier 😅 which seems like indeed would cause a breaking change otherwise. I'll close this issue then. Thanks for considering some of the points in the conversation! |
@fartinmartin btw, here a REPL for you to see how it would have been implemented, code-wise. |
Describe the problem
When using an event dispatcher generated with
createEventDispatcher()
we may need, once the callback function is executed, to chain another callback, or another event. This is especially relevant when the event callback is an asynchronous one (e.g. an API call); as of now, we can achieve this only by notifying the child component that the first callback has been executed via the props, and then react to the notification with another callback ; it would be great to being able to use.then
directly with the event dispatcher, like this :Describe the proposed solution
A possible implementation would be something like this (disclaimer: I haven't tested it yet, it probably needs to be refined to work):
Alternatives considered
Alternatively, the returned promises could carry some sort of payload; although this wouldn't be clean, because it could lead to use cases that go against the 'one source of truth' principle.
Importance
nice to have
The text was updated successfully, but these errors were encountered: