CODEPANION BLOG

How to use Async-await & Promises with Fetch in Vue.js & Vuex

Learn how to display dynamic data in Vue.js components using Vuex. Examples of using ajax in Vuex actions and Vue.js components.

In order to display some dynamic data to the user, we need to make an asynchronous request to the server from our front-end application. There are various ways of doing that, but I will show you how to use async-await function inside Vue components and inside Vuex action.

If you are using Vuex to manage your components’ state, you can put your ajax request inside the action.

Remember, you shouldn’t include ajax request inside mutation. Actions are always an asynchronous function, which means you can always add then and catch callback function after calling an action function in the component.

Some developers prefer async-await, some prefer the promise. One is not better than the other and you should use them both depend on the situation but make sure you are consistent in your code.

Reusable function for calling a server

First, let’s create a function that makes the ajax request. We will use the javascript fetch method and add parameters to the function. It should be in a separate file from Vuex. The function will return a promise with a response or error from the server. This will be a reusable function across different Vuex modules so we need to separate it in a different file.

async function http(url, 
  method = 'GET',
  data,
) {
  try {
    const response = await fetch(url, {
      method,
      data
    });
  
    return await response.json();
  } catch (error) {
    throw error;
  }
}

Vuex store

Let's add state object to our Vuex store. We will add data state for the result from the server, we will add loading which will be a flag that communication between Vue.js application and server is in progress and we will add an error state. In error we can add an error response if something went wrong in communication with the server. We will also add getters, so we can use Vuex state in Vue.js components with mapGetters function.

export default new Vuex.store({
  state: {
    data: [],
    loading: false,
    error: null
  },
  getters: {
    data: state => state.data,
    loading: state => state.loading,
    error: state => state.error
  },
});

Actions

Now we can add action method that will using our http function for making ajax call to the server. For me using an async-await function in a Vuex action looks to me like space saving in a function a little bit easier to read. But that’s just me. :) Here are both versions.

actions: {
    ['fetchDataActionExample1'] ({ commit }) {
      return new Promise((resolve, reject) => {
        commit('setLoading', true);

        http('https://apiUrlExample/data')
          .then(({ data }) => {
            commit('setUpdate', data);

            resolve(data);
          })
          .catch(error => {
            commit('setError', error);
            reject(error);
          })
          .finally(() => {
            commit('setLoading', false);
          })
        })
    },
    async ['fetchDataActionExample2'] ({ commit }) {
      try {
        commit('setLoading', true);
        const { data } = await http('https://apiUrlExample/data');
        commit('setData', data);
  
      } catch (error) {
        commit('setError', error);
        
        throw error;
      } finally {
        commit('setLoading', false);
      }
    }
  },

It's important to add try catch block in order to handle error case when using async-await. For handling error with Promise you can use catch callback function.

Before making an ajax request, it is a good UX to show to the user that data from the server is in the process of loading. So the user will now that something is happening in the background. So before calling http function we will set a loading state to true. After ajax request is made, we need to handle the response from the server. With setting await in front of the function, we are specifying that that part of the code is asynchronous so the code below will not be executed until the asynchronous function is done. This way, this ES6 feature with async-await prevent you to go to hell, I mean callback hell. :) With promises, we prevent callback hell by chaining then functions.

Mutations

Now we have to update the state with the new values. If the request to the server was successful, we need to update the data state, otherwise, we need to update the error state in other to show the user a message that something went wrong with fetching the data. Also, we need to update the loading state, so the user will know that the request to the server is finished.

Vue.js Component

We should add mapActions and mapGetters methods, so the actions and state from the Vuex can be available in a component.

import { mapActions, mapGetters } from 'vuex'

export default {
  name: 'ComponentExample',
  computed: {
    ...mapGetters({
      dataList: 'data',
      loading: 'loading',
      error: 'error'
    })
  },
  methods: {
    ...mapActions({
      asyncActionExample1: 'fetchDataActionExample1',
      asyncActionExample2: 'fetchDataActionExample2'
    })
  }
}

If you need to to show the server data immediately after the component is loaded, you should put action inside mounted lifecycle method.

...
mounted () {
  this.asyncActionExample1()
    .then(() => {
      // callback function after success call to the server 
    })
    .catch(() => {
      // error callback function
    }
},
...
//or
...
async mounted () {
    try {
      await this.asyncActionExample1();
      // success
    } catch (error) {
      // error
    }
    
}
...

You can also put asynchronous actions in methods.

...
methods: {
  onFormSubmit() {
    this.asyncActionExample1()
      .then(() => {
        // callback function after success call to the server 
      })
      .catch(() => {
        // error callback function
      })
  }
}
...
// or
...
methods: {
  async onFormSubmit() {
    try {
      await this.asyncActionExample1();
      // success
    } catch (error) {
      // error
    }
    
  }
}
...

Since the Vuex state is now accessible in the component. We can show the data in the template of the component.

<template>
  <ul>
    <li v-for="(item, i) in dataList" :key="i">
      {{ item.propertyToDisplay }}
    </li>
  </ul>
</template>
©2020 Stefan Mitrović