I have seen various types of request encapsulation and usage in my work so far, but without exception, many of them are ineffective encapsulations or make the user more complex. I plan to write an article on common encapsulation practices, so this article came about.
The following examples include Axios Fetch UseFetch (Nuxt3)
DX Experience#
As the leader within the team, it is necessary to encapsulate something that provides a good DX experience (Developer Experience). After all, what you encapsulate will be used extensively by others and will take into account the application scenarios of the entire project. The DX experience should be placed at the top.
For this reason, I have listed the following points...
- Elegant API calls
- Separation from business logic
- Ability to handle custom exceptions
- Allow prevention of default behavior
- Support for obtaining raw data
- Reduced burden of creating new interfaces
For the above points, let's analyze them one by one.
Elegant API Calls#
Let's start with an example
/* 1. API for categorizing folders */
import { getList } from "@/api/demo";
/* 2. Import all APIs using aliases */
import api from "api";
async function preload(){
try {
/* Calling using method 1 */
const result = await getList();
/* Calling using method 2 */
const result = await api.demo.getList();
// This code will be executed as long as the response is successful
} catch (error){
// All business code exceptions and httpStatus exceptions will come here if you need to handle the exceptions yourself
if(error instanceof Error){
// Toast....
}
}
}
In the above example, we differentiate between two ways of importing and using APIs. Let's dive deeper into common business scenarios such as pagination.
const result = await list();
type C = () => Promise<boolean>
/**
result's data structure, taking pagination as an example
result = {
pageNum:number
pageSize:number
list:Map<number,T>
to:C // Go to a specific page
next:C // Next page
back:C // Return to the first page
status:"loading" | "end"
}
*/
This way, we can help other business personnel reduce the time and effort spent on APIs and also avoid defining unnecessary variables. All you need to do is call an API.
Next, we will discuss how to design such a custom Hook.
API Flow#
Based on the above logic, we can deduce a specific call flow.
Page ---> Business API ---> Root Request
Root Request --> Handle httpStatus -> Handle business code ->
--> Wrap pagination -> Return
--> Do not wrap -> Return
Start encapsulating using Axios as an example#
// request.ts
import axios from "axios"
import type { AxiosRequestConfig } from "axios"
/* Create a global axios instance */
const axiosInstance = axios.create({
baseUrl:"https://api.example.com/",
timeOut:6000
});
/* Backend common response body */
export interface Request<T=null>{
code: number;
data: T;
success: boolean;
msg: string | null
}
/* Support custom ErrorCode */
export interface ErrorInstall {
[key:string|number]: string;
}
const StatusCode = 200;
export default async function request<R=null>(conf:AxiosRequestConfig,err:ErrorInstall,dialog=true){
/* In normal business logic, various custom headers are always required */
/* Clone a copy of conf here */
const config: AxiosRequestConfig = {
// Place headers above to give config default values for headers
// In the ...conf below, if there are headers, they can override the default values to ensure that headers always exist
headers:{},
...conf,
}
/* Add common headers here, such as token */
// TODO ....
const {status,data,statusText} = await axiosInstance<Request<R>>(config);
let error:Error;
/* Handling httpStatus */
if(status !== StatusCode) error = new Error(statusText);
/* If there is a business code, override the httpStatus */
if (data && data.code !== StatusCode && err[data.code]) {
error = new Error(err[data.code]);
}
/* If there is an error, trigger a common error dialog */
if(error instanceof Error){
if(dialog) showFailToast(error.message);
throw { error, data };
}
// Response
return data;
}
With this, we have completed the encapsulation of the root request.
In the next chapter, we will explain how to wrap the response content for pagination.