4

Is it possible to execute as emit as synchronous and get the result back in calling method itself. So, I want to execute next statement after the $emit completed. its as below:

Parent component has method, 
                 doCustomValidation();

child component is as follow:
methods:
{
  Save: function(){
 // Can I get the response here from parent component and then, execute 
    doGenericValidation ?

 var errorFields = this.$emit('custom-validation');  
   doGenericValidation(errorFields);   //this is generic validations
}
user3711357
  • 1,425
  • 7
  • 32
  • 54

4 Answers4

9

You shouldnt attempt to make this synchronous. Instead, you might be able to use this idea:

methods: {
    myMethod() {
        // Pass a function as a parameter.
        this.$emit('custom-validation', this.onComplete);
    },
    onComplete() {
        // Do stuff after custom-validation has completed.
    }
}

And then in whichever component uses your event:

<some-component @custom-validation="doStuff" />
<script>
...
methods: {
    doStuff(done) {
        // Your logic here. After that, call the callback function.
        done();
    }
}
...

Flame
  • 6,663
  • 3
  • 33
  • 53
  • Is it possible to pass data on this, this.$emit('custom-validation(data)', this.onComplete(resultFromChild)); ? – user3711357 Mar 26 '19 at 18:54
  • I'm not sure if it accepts multiple parameters, if not you could just do `this.$emit('custom-validation', {data: data, callback: this.onComplete})` and you will now receive an object as the parameter – Flame Mar 26 '19 at 19:04
  • There might be multiple listeners attached to an event (or none). As a result, the `onComplete()` callback might be called multiple times, or not at all with this approach – igaster Apr 05 '21 at 17:49
  • @igaster yep you're right, I guess you can shape some sort of `once` function such that the callback can only be executed once. It doesnt sound like a nice solution to be honest but then again events always allow multiple listeners. – Flame Apr 05 '21 at 22:13
3

You can make a promise based wrapper for emit and wait for its result.

Here is a pretty generic solution I've ended up creating for my projects:

    async emitPromised(method, ...params){
        let listener = this.$listeners[method] || this.$attrs[method] || this[method]
        if(listener){
            let res = await listener(...params)
            return res === undefined || res
        }
        return false
    },

Now you can use it like this:

        async onDelete(){
            this.isDeleting = true
            let res = await this.emitPromised("delete")
            this.isDeleting = false
            if(res){
                this.closeConfirm()
            }
        },

You can include this method through mixin or attach it globally to Vue instance so it's accessible to all components.

As a note, $listeners store all methods bound to this component with @ or v-on (like <MyComponet @click="onClick"/>) while $attrs store everything passed to the component using v-bind or : (like <MyComponet :click="onClick"/>)

vir us
  • 9,920
  • 6
  • 57
  • 66
2

No. $emit will always be queued. There also may be multiple listeners, so a return value does not really make sense.

As a workaround, you can send a function in as a property, and then call it directly.

Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
0

I also have the same problem statement, where I have my validations method in parent and wants to check that in child component, before performing the save action.

If you want to perform something like this. then a better option is to pass the method as props. It may not make sense to await for the response of $emit.

Example:

The method that I need to call is doCustomValidation(). of parent-component from child-component's save() method.

Parent Codebase (shown only the required codebase where names matching to the asked question):

<template>
    <child-component
      :doCustomValidation="doCustomValidation"
    />
</template>
<script lang="ts">
import ChildComponent from "@/students/ChildComponent.vue";

export default defineComponent({
    components: { 
        "child-component": ChildComponent,
    },
    methods: { 
        doCustomValidation() { 
           let isValid = false;
           // do your stuff here, even you can access the 
           // validation params and show the validations here.
           return isValid;
        }
    }
})
</script>

Child Codebase (shown only the required codebase where names matching to the asked question):

<script lang="ts">
export default defineComponent({
  props: ["doCustomValidation"],
  methods: { 
      save() {
         // calling parent validation method and 
         // this will wait for result.
         if(this.doCustomValidation()) {
            // perform Save here.
         }
      }
  }
)}
</script>
KushalSeth
  • 3,265
  • 1
  • 26
  • 29