如何的正确封装请求 和优雅的嵌入业务 (1)

在工作至今见过各种形形色色的请求封装和使用，但是无一例外很多都是无效封装，或者让使用者变的更复杂了，打算写一篇封装的常见做法所以就有了本文。

下列举例包括 Axios Fetch UseFetch (Nuxt3)

DX 体验

作为团队内的主导，封装一个符合 DX 体验 (开发人员体验)，
是非常有必要的一件事，毕竟你封装的东西要被其他人大量使用并且照顾到整个项目的应用场景，DX 体验的位置应该放在 Top。

为此我列出了一下几点 ...

优雅的 API 调用

与业务逻辑切割

带有自定义异常处理的能力

允许阻止默认行为

支持获得原始数据的能力

创建新接口的负担减少

针对于以上几点，我们逐一刨析

优雅的 API 调用

先看案例

Copy
/* 1. 文件夹分类的API */
import { getList } from "@/api/demo";

/* 2. 统一以别名导入全部API */
import api from "api";

async function preload(){
try {
/* 以1导入的方式调用 */
const result = await getList();

/* 以2导入的方式调用 */
const result = await api.demo.getList();

// 只要响应成功代码将会执行到此处

} catch (error){
// 所有的业务code异常和httpStatus异常将会走到此处 如果你需要自己处理异常的话
if(error instanceof Error){
// Toast....
}
}
}

在以上案例中我们区分了两种导入的方式和使用范例，我们再详细的深入常见业务比如 分页

Copy
const result = await list();
type C = () => Promise<boolean>
/**
result的数据结构以分页为例
result = {
pageNum:number
pageSize:number
list:Map<number,T>
to:C // 去指定页
next:C // 下一页
back:C // 返回首页
status:"loading" | "end"
}
*/

那么这样我们可以帮其他的业务人员减少在 API 上所耗费的时间和精力同时也不会定义很多没有必要的变量，你所需要做的就是调用一个 api

接下来我们会展开讲如何设计一个这样的自定义 Hook

api 走向

针对于以上逻辑我们可以简单的推出一个具体调用走向

页面 ---> 业务 API ---> 根请求
根请求 --> 处理 httpStatus -> 处理业务 code ->
--> 包装分页 -> 返回
--> 不包装 -> 返回

开始封装 以 Axios 为例

Copy
// request.ts

import axios from "axios"
import type { AxiosRequestConfig } from "axios"

/* 创建全局axios实例 */
const axiosiInstance = axios.create({
baseUrl:"https://api.example.com/",
timeOut:6000
});

/* 后端公共响应体 */
export instface Request<T=null>{
code: number;
data: T;
success: boolean;
msg: string | null
}

/* 支持自定义ErrorCode */
export interface ErrorInstall {
[key:string|number]: string;
}
const StatusCode = 200;
export default async function request<R=null>(conf:AxiosRequestConfig,err:ErrorInstall,dialog=true){

/* 在正常的业务逻辑中 总是会要求携带各式各样的自定义header */
/* 在此处clone一份conf */
const config: AxiosRequestConfig = {
// headers放置在上方即可让config拥有headers默认值
// 在下方的 ...conf 如果有headers就可以覆盖默认值让headers始终存在
headers:{},
...conf,
}
/* 在此处进行公共header添加 比如token */
// TODO ....
const {status,data,statusText} = await axiosiInstance<Request<R>>(config);
let error:Error;

/* 针对于httpStatus的处理 */
if(status !== StatusCode) error = new Error(statusText);
/* 如果有业务code则覆盖httpStatus */
if (data && data.code !== StatusCode && err[data.code]) {
error = new Error(err[data.code]);
}
/* 如果存在error则触发公共报错弹框 */
if(error instanceof Error){
if(dialog) showFailToast(error.message);
throw { error, data };
}
// 响应
return data;
}

至此我们就完成了 对于根请求封装

在下一章我们将会对讲解如何对于响应内容做分页包装处理