Request Cancelation and Abortable Function
Explore techniques for managing request cancellation and abortable functions within the Vue API layer. Understand how to handle asynchronous operations to prevent outdated responses and improve user experience, using axios cancel tokens and abortable functions in scalable applications.
We'll cover the following...
Request cancelation
There are scenarios in which it’s a good idea to cancel a request. A good example is the autocomplete functionality.
Scenario
If we have a massive database, search queries can take a moment before a response is sent back from the server. Imagine a user is trying to search for a meal, and an API call is made any time a user types something into the search input. When a response is received, a list of meals is immediately displayed. A user types a few characters, and two API requests are made to search for a meal. The first request is sent with the query, “la”, whilst the second was with “lasagne.” However, suppose the first API request has finished after the second one. In such a scenario, the search results won’t display data for the latest search, but the old one instead. That’s not a great user experience. We can solve this problem by ensuring that the first request is canceled if the second request is made.
How to cancel the request
So far, we’ve been using the axios library as an HTTP client. Requests can be canceled with axios by creating a cancel token that must be passed to an API request in the config object. However, we don’t want to start creating cancel tokens directly around the application because it would go against one of the API layers’ main principles. That is, an application doesn’t care about the internal implementation of the API layer. Therefore, we have to make sure that the cancellation is made in such a way that it’d be easy to replace it if an application switched to the fetch method or any other HTTP client.
How to implement it?
Two examples show how this can be accomplished. The first tackles the problem from outside of the API layer, whilst the second does so from inside. For the former, we’ll have an abortable function that injects a cancel token into an API function (imported from one of the API files). The abortable function returns an abort method and a wrapped API function with a cancel token injected. For the latter example, we’ll take advantage of the API wrapper around Axios. Instead of using abortable wrapping functions, we’ll initialize a cancel token by passing an abort property as part of the config object.
Abortable function
Let’s implement a feature to allow a user to search for meals. We’ll use the mealdb API. First, let’s start by adding three functions to the base api.js file.
Here’s a step-by-step explanation of the cancelation logic:
- The
didAbortfunction returns abooleanif an error object passed is an instance of aCancelerror thrown byaxios. - The
getCancelSourcecreates a new cancel source that contains a cancel method and atoken, which has to be passed to a request. - The abortable function is responsible for injecting the
canceltoken into the last argument passed to therequestfunction. The one caveat here is that aconfigobject must always be passed to an API function, even if it’s empty. In theabortablefunction, we don’t know what kind of request is being made, so we can’t determine which one should be aconfigobject based on the number of parameters. Therefore, we must assume that aconfigobject is always passed as the last argument.
We also need a new mealApi.js file that returns the searchMeals method.
We’ve used a little helper called requiredParam to ensure that an error will be thrown if the config parameter isn’t present. It’s a trick that takes advantage of default parameters. Instead of specifying a normal value, the requiredParam function is called if the config is undefined. However, if the config param is present, then the requiredParam function isn’t called. Below is the implementation for that function.
It’s time to create a component to handle search and show results. Don’t forget to add it to our routes config.
The template given below has an input field with a v-model bound to the mealQuery property and a list of meal titles received from the mealdb API.
The main part of this code is the initSearchMeals method. At the start, it calls an abort function that’s set on the $options object of the Vue instance. The first time the initSearchMeals method is called, no abort method is available. That’s why the optional chaining operator, ”?.”, is used to ensure an error like $options.abort is not a function isn’t thrown. If there’s no abort function to call, the JavaScript engine proceeds with code execution. An API method was passed directly to the withAsync helper in previous examples. This time, it’s first passed to the abortable function. The returned object contains an abort method and a wrapped API function with an injected cancel token. The abort function received is set on the $options object, so it can be called if the initSearchMeals is initialized again. If the request is canceled, we can confirm it bypasses the error object to the didAbort function.
Let’s run the following code and see how the abortable function works.
Note: The code below may take a while to run. When the server starts, go to the app URL to see the output.
<template>
<div id="nav" class="container mx-auto">
<router-view />
</div>
</template>
<style></style>
- The
api/api.jsfile contains the base API wrapper around theaxioslibrary. Other API files should use this API wrapper. It also exportsgetAbort,didAbort, andabortablemethods that can be used to handle aborting requests via the API Layer. - The
api/mealApi.jsfile contains thesearchMealsmethod, which makes a search request for meals. - The
api/constants/apiStatus.jsfile contains constants for all available API statuses:IDLE,PENDING,SUCCESS, andERROR. - The
api/composables/useApi.jsfile contains theuseApicomposable, which abstracts the handling of API states and request execution. - The
SearchMealExample.vuecomponent renders a form that can be used to search for meals. Whenever a user enters anything in the search input, themealQuerystate is updated, and the watcher initializes theinitSearchMealsmethod. This method utilizes helpers from theapi.jsfile to abort requests. When a request is aborted, a toast notification is displayed.
If we’re recreating these examples from scratch, just try to type in the search input quickly. We should see some warnings, but not too many, because the mealdb API usually responds very fast.
In the Companion App that is given below, we should see notification popups.
Note: The code below may take a while to run. When the server starts, go to the app URL to see the output.
<template>
<div id="app" class="bg-gray-100">
<GlobalSpinnerProvider>
<Layout>
<div id="nav"></div>
<router-view />
</Layout>
</GlobalSpinnerProvider>
</div>
</template>
<script>
import Layout from '@/layout/Layout'
import GlobalSpinnerProvider from '@/components/common/spinner/GlobalSpinnerProvider.vue'
import { setUser } from '@/services/stateful/userService'
/**
* Set user name for Managing State / Stateful Services example
*/
setUser({
name: 'William',
})
export default {
components: {
Layout,
GlobalSpinnerProvider,
},
}
</script>
<style lang="scss" src="@/styles/index.scss"></style>
-
The
api/api.jsfile contains the base API wrapper around theaxioslibrary. Other API files should use this API wrapper. It’s enhanced withwithAbort, which provides request cancellation functionality. -
The
api/quoteApi.jsfile contains thefetchRandomQuotemethod. -
The
AbortingRequests.vuecomponent renders some text and a quote. The abort logic is utilized by providing anabortproperty with a function as part of the config passed to an API method,fetchRandomQuote. If a request is aborted, a toast notification is shown. The toggle switch can be used to control whether requests should be aborted or not.