Front-End Vuex Actions with Async Await

We present a simple way to structure asynchronous actions of a Vuex store in a Vue.js app, using async
and await
.
Dyploma
Dyploma is a system for managing containerized applications and services on top of Kubernetes in Outbrain. Dyploma includes the concepts of:
- artifacts
- builds
- deployments
- services
Dyploma includes Java Spring backend and a Python command-line tool (CLI). The command-line tool operates through API calls to the backend.
The Dyploma Web Application
To facilitate broader adoption of containers within Outbrain, we set up to develop a web application that will have the capabilities of the Dyploma CLI.
The web application will operate by fetching data from the backend and sending operations for execution at the backend. This will be done through the same REST API used by the CLI.
A Vue.js Web Application
We chose Vue.js for constructing the web application. The app was constructed using vue-cli with the webpack template.
Vuex
Vuex is the standard state management approach for Vue.js.
Vuex Actions
An action in Vuex is where you perform interaction with APIs and commit mutations. In our web application, as in many others, most such interactions are inherently asynchronous as they include API calls.
Using ES2015 aka ES6, async
and await
makes writing asynchronous actions without feeling the asynchronous nature of these.
In terms of structuring the app, we preferred to separate the API calls to a separate API access layer and have the actions call it.
We use these async
and await
construct at the API level and the actions level.
API Access Layer
The API access layer is best separated from the store. This facilities modularity and usage of the API when no state is involved. For example, using the API for typeahead input.
Axios
We use Axios for HTTP calls to the backend as it is based on promises.
We configure axios like:
import axios from 'axios';
const axiosConfig = {
baseURL: config.API_URL,
};
const HTTP = axios.create(axiosConfig);
And then use it to define the API calls.
The API Calls
Many APIs require multiple calls to compose an item of information. So we compose those calls with Promise.all
and await
:
export default {
async getDeployments(offset, limit) {
try {
const deployments = await HTTP.get(`deployments/find/limit/${page.limit}/offset/${page.offset}`);
const enrichedDeployments = await Promise.all(_.map(deployments, async (deployment) => {
const service = await services
.loadService(deployment.serviceId);
const environment = await environments
.loadEnvironment(deployment.environmentId);
const kubeCluster = await clusters
.loadCluster(deployment.kubeClusterId);
return { ...deployment,
service: service.name,
environment: environment.name ,
kubeCluster: kubeCluster.name,
};
}));
return enrichedDeployments;
} catch (error) {
console.log(error);
return (null);
}
}
So we use async
and await
for full fledged API calls.
Handling Errors
We catch errors, such as network connectivity, or server-side failures at the API access layer, and present a clean interface For the actions.
Asynchronous Actions
Asynchronous actions await
for the API calls and then commit the results:
import deployments from '@/api/deployments';
import * as types from '@/store/modules/deployments/mutation-types';
export default {
async getDeployments({ commit, state }, { skip, limit }) {
// signal to the views that we are loading data
commit(types.PROCESSING_DEPLOYMENTS);
const results = await deployments.getDeployments(skip, limit);
if (results) {
commit(types.RECEIVED_DEPLOYMENTS, { results, noMoreData: results.length < limit });
} else {
commit(types.DEPLOYMENTS_FETCH_ERRORS);
}
},
See use async
and await
for the Vuex actions.
Conclusion
It all feels serial and synchronous. Much simpler. Much cleaner.
Yoram Kornatzky