5

I am using

"axios": "^0.19.0",
"vue": "^2.6.10",
"vuex": "^3.1.1"

My vuex action looks like this and calls a remote interface using axios. The request does work and it receives a valid response.

skipQuestion({commit}, payload) {
    let params = {
        answer: {
            id: payload.id,
            skipped: true,
        }
    };

    return new Promise((resolve, reject) => {
        commit(UPDATE_LOADING, true);
        Remote.put(`/answer.json`, params)
            .then((response) => {
                commit(UPDATE_LOADING, false);
                commit(SKIP_QUESTION, payload.id);
                resolve();
            })
            .catch((error) => {
                commit(UPDATE_LOADING, false);
                reject(error);
            })
    })
},

The component Question does have the following method skip, which calls the vuex action skipQuestion and should emit the skip event to the parent component.

...mapActions(['skipQuestion']),
skip(evt) {
    let payload = { id: this.question_id };
    this.skipQuestion(payload).then( () => {
        this.$emit('skip', this.uuid);
    }).catch( (error) => {
        console.log(error);
    });
},

The problem is, that the skip event is not emitted to the parent when using it within the then block of the action. The vue developer console for chrome also confirms, that the skip event was fired. If I put the emit outside the block, everything works. Any suggestions whats going wrong?

Edit 1

Also tried the following code and both log statements are printed to the console.

skip(evt) {
    let payload = { id: this.question_id };
    let vm = this; 
    this.skipQuestion(payload).then( () => {
        console.log('before skip emit');
        vm.$emit('skip', this.uuid);
        console.log('after skip emit');
    }).catch( (error) => {
        console.log(error);
    });
},

Edit 2

Here is the template code which is used to listen to the child events:

<question v-bind="question"
          :key="question.uuid"
          v-if="questionReady"
          v-on:skip="onSkipQuestion"
          v-on:answer="onAnswerQuestion">
</question>

As told before, if not using the Promise/then-block returned by the axios request, the emit does work. Below you'll find the method which should be called:

methods: {
  onSkipQuestion(payload) {
    // this code is not executed
    console.log('onSkipQuestion')

    //....
  },


  //....
}      
anka
  • 3,817
  • 1
  • 30
  • 36
  • 1
    Maybe not the definitive solution to your problem, but you should definitely try switching to await / async instead of using raw promises – coyotte508 Oct 18 '19 at 11:17
  • I'm assuming that you have on your parent component something like `v-on:skip=somfunc` to listen to the `skip` event. is that right? – Carlos Crespo Oct 22 '19 at 09:46
  • Yes of course, I also tried `@skip=somefunc`. As mentioned if I emit it outside the `then` block everything works fine. – anka Oct 22 '19 at 13:18
  • Can you add your template code ? – Pierre Burton Oct 22 '19 at 14:27
  • Maybe try to use ```.bind()``` for this method, then maybe your parent commponent will also see this event. Or maybe try to make ```skip``` arrow function inside ```data() {return { skip: () => ....}} ``` – Krzysztof Kaczyński Oct 26 '19 at 09:46

4 Answers4

4

Summarize your information given:

The vue developer console for chrome also confirms, that the skip event was fired.


TL;DR


Basically you can debug using debugger statement:

skip(evt) {
    let payload = { id: this.question_id };
    this.skipQuestion(payload).then( () => {
        debugger; // scope -> _events & scope -> $parent.componentInstance
        // or console.log(JSON.stringify(this._events))
        this.$emit('skip', this.uuid);
    }).catch( (error) => {
        console.log(error);
    });
},

Then you know whats happening.


Things to check:

  • valid this scope

  • valid parent

  • actually triggered as resolved

  • vm._events registered

Community
  • 1
  • 1
Estradiaz
  • 3,483
  • 1
  • 11
  • 22
2

You have lost the reference to this inside the then block. The reference is now the callback function called. Instead do this

 ...mapActions(['skipQuestion']),
    skip(evt) {
        let payload = { id: this.question_id };
        let vm = this; // Preserve Vue instance for use inside block
        this.skipQuestion(payload).then( () => {
            vm.$emit('skip', vm.uuid);
        }).catch( (error) => {
            console.log(error);
        });
    },
Varun Agarwal
  • 1,587
  • 14
  • 29
  • Thanks, I tried that before but it doesn't change anything. The event is not emitted to the parent. – anka Oct 18 '19 at 11:58
  • strange.... Have you confirmed the `then` blocked is being called by adding in a console.log statement? – Varun Agarwal Oct 18 '19 at 11:59
  • Yes, I added a `console.log` before and after the `emit` and both are printed within the `then` block. Did update the code example. – anka Oct 18 '19 at 12:06
  • If you change `this` to **vm**, then inside the emit statement `this.uuid` should be changed too. – Tzahi Leh Oct 22 '19 at 13:46
1

Maybe the problem is that you don't return resolve() or reject() in skipQuestion().

Anyway, assuming your application uses ES2017 (or higher), or if not then Babel, I would refine the function to use async / await structure so that this would not be at any risk (referring to Varun's answer), and not in any risk if skipQuestion() doesn't return anything, like as is currently:

async skip(evt) {
    let payload = { id: this.question_id };
    try {
      let result = await skipQuestion(payload)
      this.$emit('skip', this.uuid);
    } catch(error){
      console.log(error);
    }
}

Not returning resolve / reject is a known source of bugs in case of Promise((resolve, reject) => {...}) syntax

Tzahi Leh
  • 2,002
  • 1
  • 15
  • 27
  • Thanks for the answer. Currently we are using `Babel`. We tried your update code and also removed the returning of the extra `Promise`, but still the emit is not promoted to the parent component. – anka Oct 23 '19 at 07:43
  • What do you mean "removed the returning of the extra Promise"? Can you edit your question adding the changes you've done? – Tzahi Leh Oct 23 '19 at 08:02
  • The store actions now returns `return Remote.put(`/answer.json`, params) .then((response) => { commit(UPDATE_LOADING, false); }) .catch((error) => { commit(UPDATE_LOADING, false); })` – anka Oct 23 '19 at 08:56
  • You don't need to remove the promise syntax.. Anyway, please return something in the `then` and `catch` blocks too! You use `then` and `catch` when initiating skipQuestion() and I assume it doesn't get hit because nothing is being returned – Tzahi Leh Oct 23 '19 at 13:29
  • Can you give an example to your statement "Not returning resolve / reject is a known source of bugs" - this should be not the case o0? At least for the promise state - unless you run blocking code after resolving/rejecting within the promise... i am curious – Estradiaz Oct 26 '19 at 08:01
  • I'm afraid this is not the case here indeed. For further explanation about how it can get messy, please read Ori's answer in [this thread](https://stackoverflow.com/a/32536083/5762431) – Tzahi Leh Oct 29 '19 at 11:44
1

I had this exact same problem and could not for the life of me figure out what was going on. All I can think of is that somehow the component is losing the ability to emit events while awaiting the promise resolution.

Anyway, my solution was to emit the promise itself, like this:

skip(evt) {
    let payload = { id: this.question_id };
    this.$emit('skip', skipQuestion(payload));
}

And on the parent, you can do

... @skip="receive_skip($event)" ...

...

methods: {
    receive_skip(skipped) {
        skipped
        .then((data) => {
            // do something on success
        })
        .catch((err) => {
            // do something on fail
        });
    }
}

It's not as clean and elegant, but it gets the job done.

Dausuul
  • 154
  • 7