Closed AyloSrd closed 1 year ago
Note: If you define the event as a prop instead of an emit, you can do that today (onXXX props are what v-on:XXX is translated to by th compiler):
<template>
<button v-on:click="handleClick">Post and Close</button>
</template>
<script>
export default {
props: ['onPost', 'onClose'],
methods: {
handleClick(a) {
this.onPost('post')
.then(res => res && this.onClose())
},
}
}
</script>
or a slightly more
Usage in the parent would stay the same, as v-on:post
will end up as an onPost
prop on the generated vnode.
@LinusBorg , thanks for your note. If I understand well what you suggest, I believe you can do it only if the two callbacks are already returning a promise, am I wrong ?
What I would like to add as feature is for $emit
to return the promise regardless of whether the callback is async
or not. Also the Promise should resolve in true
or false
according to whether the callback has run or not, without sharing the actual return of the callback (so that that info is not sneaked to the child in breach of the one source of truth principle).
I understand. For now, you could write such an emit function yourself. Something like:
app.config.globalProperties.$myEmit = function (prop, ...args) {
const prop = this.$props[prop]
if (!prop || typeof prop !== 'function') { return Promise.resolve(false) }
return Promise.resolve(prop(...args))
}
Usage:
<template>
<button v-on:click="handleClick">Post and Close</button>
</template>
<script>
export default {
props: ['onPost', 'onClose'],
methods: {
handleClick(a) {
this.$myEmit('onPost')
.then(res => res && this.$myEmit('onClose'))
},
}
}
</script>
Does this feature have any priority in vue 3?
Since Vue's philosophy is "Props to pass down data, emits to pass up data", passing callback functions as props feel like an anti-pattern to me. It would be really great to be able to know if an emit was added to the component (such as $listeners
in vue 2) and then being able to await the emit in the child component.
I have found a workaround; the pattern looks like this. @AyloSrd
//# Parent Level
<template>
<customer-picker
@post="doRequest">
</customer-picker>
</template>
<script>
export default {
methods: {
doRequest: function(resolve){
setTimeout(() => {
resolve('response from request')
}, 6000);
}
}
}
</script>
//# Child Level
<template>
<button v-on:click="handleClick">Post and Close</button>
</template>
<script>
export default {
name: 'customer-picker',
emits: ['post'],
methods: {
async handleClick(a) {
const dataset = await new Promise(resolve => {
this.$emit('post', resolve);
});
//# Final
console.log(dataset);
},
}
}
</script>
What problem does this feature solve?
When working with Component Events emitted with
$emit
, we may need, once the callback function is executed, to chain another callback, or another Component 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 via additional props, that the first callback has been executed, and then react to the notification with another callback; this requires though a lot of boilerplate code, and can result in a very complex props flow and inner logic.
It would be great if
$emit
retuned a Promise, that resolved totrue
when the callback has been executed, orfalse
in all the other cases: the callback is null or undefined, the event has a.once
modifier and was executed previously, an error is thrown, etc.It would allow to create components that are less dependent on their parents' input, to reduce the amount of boilerplate code, as well as the complexity coming from having to manage a more complex props flow, and to obtain a leaner inner logic. The "one source of truth" principle would remain unbreached, as the Promise is not returning the actual value of the callback, but only notifying its execution.
What does the proposed API look like?
In the child component, the easiest case scenario :
or a slightly more complex one :
and, in both cases the parent will have this: