Mastering Axios: Elegant Encapsulation Techniques for Developers
Written on
Chapter 1: Introduction to Axios Encapsulation
Many articles discuss Axios encapsulation, yet doubts often arise during implementation. The question remains: do these advanced encapsulations complicate Axios usage?
High-quality encapsulation should possess the following traits:
- Enhancement of Native Framework Limitations: Identify and rectify the shortcomings of the native framework without introducing new issues.
- Preservation of Original Functionality: Ensure that all native API functions can still be accessed through the new API.
- Ease of Understanding: Developers familiar with the native framework should quickly adapt to the re-encapsulated APIs.
Next, I will delve into low-level encapsulation before exploring more elegant methods.
Section 1.1: Low-Level Encapsulation
Low-level encapsulation involves creating a new API that simplifies specific methods while limiting parameter exposure. Here’s an example code snippet:
export const post = (url, data, params) => {
return new Promise((resolve) => {
axios
.post(url, data, { params })
.then((result) => {
resolve([null, result.data]);})
.catch((err) => {
resolve([err, undefined]);});
});
};
In this code, the Axios post method is encapsulated to handle errors without requiring try-catch blocks. However, this method only exposes three parameters: url, data, and params. While these are adequate for many straightforward requests, they may not suffice for more complex scenarios, such as needing to set a longer timeout for slow responses.
For instance, in a specific situation, you might use:
axios.post("/submit", form, { timeout: 15000 });
There are various special cases to consider, including:
- Uploading forms with both data and files, necessitating the headers["Content-Type"] to be set to "multipart/form-data", along with the onUploadProgress attributes.
- Managing data races by using cancelToken or signal. Although some suggest using interceptors for this, it could lead to complications, especially when multiple dropdowns request the same options.
Some argue that such special interfaces are rare, but a project’s evolving requirements can introduce new complexities. To prepare for future enhancements, keep the encapsulation aligned with the original API to avoid cumbersome modifications later.
Section 1.2: Creating Axios Instances
You can encapsulate Axios by creating instances or defining a custom Axios class. Here's an example:
const createAxiosByInterceptors = (config) => {
const instance = axios.create({
timeout: 1000,
withCredentials: true,
...config,
});
instance.interceptors.request.use(...);
instance.interceptors.response.use(...);
return instance;
};
class Request {
instance: AxiosInstance;
interceptorsObj?: RequestInterceptors;
constructor(config: RequestConfig) {
this.instance = axios.create(config);
this.interceptorsObj = config.interceptors;
this.instance.interceptors.request.use(
this.interceptorsObj?.requestInterceptors,
this.interceptorsObj?.requestInterceptorsCatch,
);
this.instance.interceptors.response.use(
this.interceptorsObj?.responseInterceptors,
this.interceptorsObj?.responseInterceptorsCatch,
);
}
}
These methods allow for creating multiple Axios instances with different configurations and interceptors. However, maintaining a single Axios instance is advisable, as multiple instances can complicate code understanding and increase the onboarding time for new developers.
Section 1.3: Handling Multiple Configurations
If you have different configurations for various interfaces, define constants for each configuration. For example:
const configA = { /.../ };
const configB = { /.../ };
axios.get("api1", configA);
axios.get("api2", configB);
This approach is more intuitive and allows readers of the code to easily identify differences compared to using multiple Axios instances.
Section 1.4: Managing Multiple Interceptors
If different interceptors are needed, you can mount them on a globally unique Axios instance and selectively execute them based on a custom attribute:
instance.get("/api", { enableIcp: true });
In the interceptor, you can control logic execution based on this attribute:
instance.interceptors.request.use((config) => {
if (config.enableIcp) {
//...}
return config;
});
This method allows for precise control over which interceptors are executed without cluttering the codebase.
Chapter 2: Elegant Encapsulation
The elegant encapsulation approach simplifies usage as follows:
apis[method][url](config);
The response type will be:
{
data: null | T;
err: AxiosError | null;
response: AxiosResponse | null;
}
Section 2.1: Intelligent URL Derivation
This method supports intelligent parameter deduction based on the URL. If a required parameter is missing, TypeScript will raise an error.
Section 2.2: Smart Result Derivation
For example, for a GET request at /admin, the expected return type would be:
{ admins: string[] }
Section 2.3: Error Handling without Try-Catch
You can simplify error handling without needing to write try-catch blocks:
const getAdmins = async () => {
const { err, data } = await apis.get['/admins']();
if (err) {
//...
return;
}
setAdmins(data!.admins);
};
Section 2.4: Support for Path Parameters
For path parameters, intelligent deduction is also supported:
const getAccount = async () => {
const { err, data } = await apis.get["/account/{username}"]({
args: { username: "123" },});
if (err) return;
setAccount(data);
};
Finally, thank you for reading! I look forward to your continued support and hope you enjoy more insightful articles.
For additional content, check out PlainEnglish.io. Sign up for our free weekly newsletter and follow us on Twitter, LinkedIn, YouTube, and Discord. If you're interested in scaling your software startup, explore Circuit.
The video titled "How to Encapsulate Axios Elegantly with React JS" provides a comprehensive guide on effective encapsulation techniques.
The second video, "How to Make HTTP Requests like a Pro with Axios," further enhances your understanding of Axios usage in real-world applications.