Alexander Kim
Alexander Kim

Reputation: 18402

$emit inside async method - Vue2

Here's my child's component async method:

async created () {
  this.$parent.$emit('loader', true)
  await this.fetchData()
  this.$parent.$emit('loader', false)
}

fetchData does an axios get call, to fetch data from API. However in a vue-devtools (events tab) i can only see the events, after i change the code and it hot reloads. Also i've set up console.log() in a parent component:

mounted() {
  this.$on('loader', (value) => {
    console.log(value)
  })
}

And i can see only false in a console. My purpose is to emit loader set to true (so i can show the loader), then set it to false, when data is fetched.

My fetchData method:

import http from '@/http'

fetchData() {
  return http.getOffers().then((resp) => {
    this.offersData = resp.data
  })
}

Contents of http.js:

import axios from 'axios'
import config from '@/config'

const HTTP = axios.create({
  baseURL: config.API_URL
})

export default {
  /* calculator */
  getOffers() {
    return HTTP.get('/url')
  }
}

If i directly use return axios.get() in async created(), then it works. Problem is in this imported http instance.

Final solution

One of the problems was using different lifecycles, thanks to Evan for mentioning this.

Another problem was with async / await usage, changes to a fetchData() method:

import http from '@/http'

async fetchData() {
  await http.getOffers().then((resp) => {
    this.offersData = resp.data
  })
}

I had to make this method async and use await on axios request, since await is thenable, it does work. Also i've spotted an issue in https.js:

export default {
  /* calculator */
  getOffers() {
    return HTTP.get('/url')
  }
}

It returns HTTP.get(), not a promise itself, i could have used then here, and it would work, but, for flexibility purposes i didn't do that.

But, still, i don't get why it didn't work:

fetchData() {
  return http.getOffers().then((resp) => {
    this.offersData = resp.data
  })
}

Isn't it already returning a promise, since it's chained with then... So confusing.

Retested again, seems like return is working, lol.

Upvotes: 3

Views: 9566

Answers (3)

Evan Kennedy
Evan Kennedy

Reputation: 4175

The issue here is that created on the child component is getting called before mounted on the parent component, so you're beginning to listen after you've already started your Axios call.

The created lifecycle event method does not do anything with a returned promise, so your method returns right after you begin the Axios call and the rest of the vue component lifecycle continues.

You should be able to change your parent observation to the created event to make this work:

created() {
  this.$on('loader', (value) => {
    console.log(value)
  })
}

If for some reason you need to do something that can't be accessed in created, such as accessing $el, I'd suggest moving both to the mounted lifecycle hook.

Upvotes: 4

Sebastian Scholl
Sebastian Scholl

Reputation: 1095

I'd simply suggest restructuring your method, as there isn't really a need to make an async method since axios itself is asnychronus.

If you already have the fetchData method defined, and the goal is to toggle the loader state when a call is being made, something like this should do.

fetchData () {
    this.$parent.$emit("loader", true)

    axios.get(url)
        .then(resp => {
            this.data = resp
            this.$parent.$emit("loader", false)
        })
}

Of course these then statements could be combined into one, but it's the same idea.

Edit: (using the parent emit function)

fetchData () {
    this.loader = true

    axios.get(url)
        .then(resp => this.data = resp)
        .then(() => this.loader = false)
}

Upvotes: 2

Sølve
Sølve

Reputation: 4406

If what you are trying to achieve is to tell the direct parent that it's no longer loading, you would have to emit to the same instance like so

async created () {
  this.$emit('loader', true)
  await this.fetchData()
  this.$emit('loader', false)
}

By removing the$parent, you will emit from the current component.

--Root
 --My-page.vue
   -Some-child.vue

Now you will emit from some-child.vue to my-page.vue. I have not tried, but theoretically what you are doing by emiting via parent: (this.$parent.$emit('loader', false)) You are emitting from my-page.vue to root.

So If you have a $on or @loader on the component like so: <Some-child @loader="doSomething"/>, This will never run due to you emitting from the parent.

Upvotes: 1

Related Questions