简体   繁体   中英

Wait for Ajax response data in Vue.js

I have a Vue component where I am trying to fetch some data from an API using axios.

<template>
    <div>
        This is Default child component
        {{tools[0].name}}
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  methods: {
      fetchData() {
        const customJs = new CustomJS();
        return customJs.getTools();
      }
  },
  created() {
    this.tools = this.fetchData(); //preferably need to wait here wait for response
  }
}
</script>

The getTools() function is in a different JS file outside the Vue component file which makes the API call using axios.get.

getTools(id = 0){
    this.apiTool += (id > 0) ? id : '';
    axios.get(this.apiTool, {
    })
    .then(function (response) {
        console.log(response.data);
        return response.data;
    })
    .catch(function (error) {
        console.log(error);
    });
}

The problem is, {{tools}} is undefined since the getTools() takes some time to return the response data. How can wait for the response data and then return?

Try the code below: so the code will render only when its actually loaded

<div v-if="tools">
    This is Default child component
    {{tools[0].name}}
</div>

Usually for that, I am using a loader to show the user that a request is in progress

<div v-if="loading">
  <loader /> //a loader component
</div>

<div v-else>
  // show the template since request finished
</div>

and the script

export default {
  data: () => ({
    loading: false
  }),

  created() {
   this.loading = true
   axios.get('api') //your request
   .then(response => console.log(response))
   .finally(() => (this.loading = false)) //when the requests finish
  }

}

If you don't like the way above, you can use beforeEnter so the route will load only when the request finishes:

{
  path: '/my-route',
  component: YourComponent,
  props: true,
  beforeEnter (to, from, next) {
    axios.get('api-request')
     .then(response => {
      to.params.data = response //we pass data through props to the component
      next()
     })
  }
}
<template>
    <div v-if="isGetTools">
        This is Default child component
        {{tools[0].name}}
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  data: function () {
    return {
      isGetTools: false
    }
  },
  methods: {
      fetchData() {
        const customJs = new CustomJS();
        this.tools = customJs.getTools();
        this.isGetTools = true;
      }
  },
  created() {
    this.fetchData(); //preferably need to wait here wait for response
  }
}
</script>

Try to add v-if in your div. And update the isGetTools to true after getting the result from AXIOS

You need to return a promise from your request

<template>
<div>
    This is Default child component
    {{tools[0].name}}
</div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
      tools: []
  },
  methods: {
      fetchData() {
         const customJs = new CustomJS();
         return new Promise((resolve, reject) => {
             customJs.getTools()
                 .then(res => resolve(res))
                 .catch(err => reject(err))
        })
     }
  },
  created() {
      this.fetchData().then(res => {
         this.tools = res);
      } //preferably need to wait here wait for response
    }
   }
 </script>

Give a try to retrieve data on mounted

    <template>
    // try adding this condition on div element.
                <div v-if="tools.length">
                    This is Default child component
                    {{tools[0].name}}
            </div>
        </template>

        <script>
        import { CustomJS } from '../js/custom.js';

        export default {
          name: 'HomeContent',
          props: {
            tools: []
          },
          methods: {
              fetchData() {
                const customJs = new CustomJS();
                return customJs.getTools();
              }
          },
          mounted: function () {
            this.tools = this.fetchData();    
            // or    
            // const customJs = new CustomJS();
            // this.tools =  customJs.getTools();
          }
        }
        </script>

What you want to do is to define your getTools function as a promise like this:

getTools (id = 0) {
  return new Promise((resolve, reject) => {
    this.apiTool += (id > 0) ? id : '';
    axios.get(this.apiTool, {
    })
    .then(function (response) {
        console.log(response.data);
        resolve(response.data);
    })
    .catch(function (error) {
        console.log(error);
        reject(error)
    });
  })
}

Then you can use it in your component code as:

<template>
    <div>
        This is Default child component
        {{tools[0].name}}
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  methods: {
      fetchData() {
        const customJs = new CustomJS();
          customJs.getTools().then((result) => {
            return result;
          }
        )

      }
  },
  created() {
    this.tools = this.fetchData(); //preferably need to wait here wait for response
  }
}
</script>

Or using async/await:

<template>
    <div>
        This is Default child component
        {{tools[0].name}}
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  methods: {
      async fetchData() {
      const customJs = new CustomJS();
      return await customJs.getTools()  
      }
  },
  created() {
    this.tools = this.fetchData(); //preferably need to wait here wait for response
  }
}
</script>

I don't know why this question pops up in my "timeline" and haven't got the accepted answer yet so I'll answer it anyway. The problem seemed to be with the understanding of the OP with asynchronous code in JS. Here's the fix (there're 3) to make it better:

<template>
    <div v-if="tools.length">
        {{tools[0].name}}
    </div> 
    <div v-else> // I. Use v-if/v-else to conditionally show data, in Vue 3 there will be another feature called "suspense" borned to do those things: https://vueschool.io/articles/vuejs-tutorials/suspense-new-feature-in-vue-3/
        This is Default child component
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  methods: {
    async fetchData() {
      const customJs = new CustomJS();
      this.tools = await customJs.getTools(); 
      // II. The OP assigned a promise to this.tools, this.tools wasn't being assigned to any actual data at all, because: 
      // 1. getTools() didn't return any data
      // 2. even if it returned data, it wasn't being promise-resolved (by await/then) before assigned to this.tools
    }
  },
  created() {
    this.fetchData();
  }
}
</script>

async getTools(id = 0){
  this.apiTool += (id > 0) ? id : '';

  try {
    const response = await axios.get(this.apiTool, {});
    console.log(response.data);
    return response.data; 
    // III. The OP didn't return any fetched data at all, it just called API then do nothing. All the returns were returned in the arrow functions, the actual function getTools didn't get any return

  }
  catch (err) {
    console.log(err)
  }
},

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM