过去的一段时间,我都认为 接口请求 封装是前端的必修课。 只要是写过生产环境前端代码的人,应该都脱离不了异步接口请求,那么 接口请求 的 封装 是必经之路。
直到前些天,我们屋某个美团写后台的小姑娘问我前端问题时。我才发现她们代码中的 接口请求 ,都是没有任何的封装,直接采用以下方式进行:
axios.post(`/api/xxxx/xxxx?xxx=${xxx}`, { ...data })
.then((result) => {
if (result.errno) {
....
} else {
....
}
})
.catch((err) => {
....
})
这样写也不是说不好,在某种程度上,这增加了代码的可读性。 但是我们大多数页面需要的接口都不止一个,那么我们的组件中极有可能出现 数十上百 行重复代码。
那么随着请求的体量越来越大,我们的项目便越来越难以维护。
const [e, r] = await api.getUserInfo(userid)
if (!e && r) this.userInfo = r.data.userinfo
上面是我们最终的实现效果。
接下来,我将带大家一步一步封装一套属于我们自己的 接口请求工具 ,同时也希望大家分享更好的思路。
注:
- 如果你希望直接看源码,请翻到 《完整代码》
- 这里以
axios
作示范,同样换成fetch
、 小程序的request
都是可以的- 我将会采用
typeScript
书写这段教程,如果你不需要,忽略掉对应的类型即可
在我们正式开发前,首先需要清楚请求一个接口都做了什么。
为此,消耗了两个小时时间,做了一个请求流程图,以便于我们后续进行需求分析(小声bb:Processon真难用 )
有了一个清晰的请求流程图,我们便可以区分出来两块重要的内容来进行拆分: 基础请求流程 、 拦截器 。
接下来我们将两块儿内容展开讲。
基础请求流程,我们大致可以分为三块, 一是 请求进入请求拦截前 、二是 真正发起的请求 、三是 请求从响应拦截出来后 。
这其中可以归为两类, 一类是 针对单独接口的处理 二类是 针对所有接口需要的内容
针对单独接口的处理
请求前的参数处理
请求后的返回值处理
针对所有接口的处理
Post
Get
Put
Del
拦截器,我们大致可以分为两类, 一类是 请求接口前的统一处理(请求拦截) 、 一类是 请求接口后的统一处理(响应拦截)
请求拦截
请求调整
用户标识
响应拦截
网络错误处理
授权错误处理
普通错误处理
代码异常处理
随着我们的 Api
越来越多,我们可能需要给他们不同的分类,但我们并不希望每次调用都从不同的文件夹引入不同的 Api
,因此在 基础请求 + 拦截器 之外,我们还需要一个封包操作。
随着我们要做的内容越来越多,我们希望它有一个顺序以便于我们按部就班的开发(相信大家对开发中出现的不确定性都深恶痛绝)。 以便于我们按照流程,无意外、无惊喜 的完成此次封装。
在我们的开发中,我们基本要遵循先处理通用内容在处理个性化内容的逻辑:
这里大家可能意外为什么 Post、Put、Del 的处理在最后开发: 因为大多数情况,我们开发中希望所编写的内容有一个及时的回馈。
举个栗子:我在生活中发现 → 我们学习吉他时,大多数人半途而废了。但坚持下来的人基本无一例外的通过吉他在不同的阶段都获得了好处,包括但不限于 异性 的夸奖、舍友的鼓掌、 get女朋友 。 这也是我们在毕业独处后,很难学会弹吉他的原因(无处炫耀)。
因此,我们需要让所开发的内容尽快达到可用的阶段(MVP)。
按照我们之前定好的顺序,按部就班的开发⑧!
我们希望以 const [e, r] = await api.getUserInfo(id)
的方式调用,代表着我们需要保证返回值稳定的返回 [err, result]
,所以我们需要在请求无论成功失败时,都以 resolve
方式调用。
同时,我们希望我们可以处理返回值,因此在这里封装了 clearFn
的回调函数。
type Fn = (data: FcResponse<any>) => unknown
interface IAnyObj {
[index: string]: unknown
}
interface FcResponse<T> {
errno: string
errmsg: string
data: T
}
const get = <T,>(url: string, params: IAnyObj = {}, clearFn?: Fn): Promise<[any, FcResponse<T> | undefined]> =>
new Promise((resolve) => {
axios
.get(url, { params })
.then((result) => {
let res: FcResponse<T>
if (clearFn !== undefined) {
res = clearFn(result.data) as unknown as FcResponse<T>
} else {
res = result.data as FcResponse<T>
}
resolve([null, res as FcResponse<T>])
})
.catch((err) => {
resolve([err, undefined])
})
})
请求拦截中,我们需要两块内容,一是 请求的调整 ,二是 配置用户标识
const handleRequestHeader = (config) => {
config['xxxx'] = 'xxx'
return config
}
const handleAuth = (config) => {
config.header['token'] = localStorage.getItem('token') || token || ''
return config
}
axios.interceptors.request.use((config) => {
config = handleChangeRequestHeader(config)
config = handleConfigureAuth(config)
return config
})
响应错误由三类错误组成:
因此,要优雅的处理响应拦截,我们必须先将三类错误函数写好,以便于我们增强代码扩展性及后期维护。
const handleNetworkError = (errStatus) => {
let errMessage = '未知错误'
if (errStatus) {
switch (errStatus) {
case 400:
errMessage = '错误的请求'
break
case 401:
errMessage = '未授权,请重新登录'
break
case 403:
errMessage = '拒绝访问'
break
case 404:
errMessage = '请求错误,未找到该资源'
break
case 405:
errMessage = '请求方法未允许'
break
case 408:
errMessage = '请求超时'
break
case 500:
errMessage = '服务器端出错'
break
case 501:
errMessage = '网络未实现'
break
case 502:
errMessage = '网络错误'
break
case 503:
errMessage = '服务不可用'
break
case 504:
errMessage = '网络超时'
break
case 505:
errMessage = 'http版本不支持该请求'
break
default:
errMessage = `其他连接错误 --${errStatus}`
}
} else {
errMessage = `无法连接到服务器!`
}
message.error(errMessage)
}
const handleAuthError = (errno) => {
const authErrMap: any = {
'10031': '登录失效,需要重新登录', // token 失效
'10032': '您太久没登录,请重新登录~', // token 过期
'10033': '账户未绑定角色,请联系管理员绑定角色',
'10034': '该用户未注册,请联系管理员注册用户',
'10035': 'code 无法获取对应第三方平台用户',
'10036': '该账户未关联员工,请联系管理员做关联',
'10037': '账号已无效',
'10038': '账号未找到',
}
if (authErrMap.hasOwnProperty(errno)) {
message.error(authErrMap[errno])
// 授权错误,登出账户
logout()
return false
}
return true
}
const handleGeneralError = (errno, errmsg) => {
if (err.errno !== '0') {
meessage.error(err.errmsg)
return false
}
return true
}
当我们将所有的错误类型处理函数写完,在 axios
的拦截器中进行调用即可。
axios.interceptors.response.use(
(response) => {
if (response.status !== 200) return Promise.reject(response.data)
handleAuthError(response.data.errno)
handleGeneralError(response.data.errno, response.data.errmsg)
return response
},
(err) => {
handleNetworkError(err.response.status)
Promise.reject(err.response)
}
)
基于上面的几类通用处理,我们这个请求的封装基本已经可用了。
但是我们还有一些额外的操作无处存放(参数处理、返回值处理),且我们并不想将他们耦合在页面中每次调用进行处理,那么我们显然需要一个位置来处理这些内容。
import { Get } from "../server"
interface FcResponse<T> {
errno: string
errmsg: string
data: T
}
type ApiResponse<T> = Promise<[any, FcResponse<T> | undefined]>
function getUserInfo<T extends { id: string; name: string; }>(id): ApiResponse<T> {
return Get<T>('/user/info', { userid: id })
}
用户数据: api/path/user.ts
import { Get } from "../server"
export function getUserInfo(id) { ... }
export function getUserName(id) { ... }
export const userApi = {
getUserInfo,
getUserName
}
订单数据: api/path/shoporder.ts
import { Get } from "../server"
function getShoporderDetail() { ... }
function getShoporderList() { ... }
export const shoporderApi = {
getShoporderDetail,
getShoporderList
}
api/index.ts
import { userApi } from "./path/user"
import { shoporderApi } from "./path/shoporder"
export const api = {
...userApi,
...shoporderApi
}
export const post = <T,>(url: string, data: IAnyObj, params: IAnyObj = {}): Promise<[any, FcResponse<T> | undefined]> => {
return new Promise((resolve) => {
axios
.post(url, data, { params })
.then((result) => {
resolve([null, result.data as FcResponse<T>])
})
.catch((err) => {
resolve([err, undefined])
})
})
}
// Put / Del 同理
业务处理函数: src/api/tool.ts
const handleRequestHeader = (config) => {
config['xxxx'] = 'xxx'
return config
}
const handleAuth = (config) => {
config.header['token'] = localStorage.getItem('token') || token || ''
return config
}
const handleNetworkError = (errStatus) => {
let errMessage = '未知错误'
if (errStatus) {
switch (errStatus) {
case 400:
errMessage = '错误的请求'
break
case 401:
errMessage = '未授权,请重新登录'
break
case 403:
errMessage = '拒绝访问'
break
case 404:
errMessage = '请求错误,未找到该资源'
break
case 405:
errMessage = '请求方法未允许'
break
case 408:
errMessage = '请求超时'
break
case 500:
errMessage = '服务器端出错'
break
case 501:
errMessage = '网络未实现'
break
case 502:
errMessage = '网络错误'
break
case 503:
errMessage = '服务不可用'
break
case 504:
errMessage = '网络超时'
break
case 505:
errMessage = 'http版本不支持该请求'
break
default:
errMessage = `其他连接错误 --${errStatus}`
}
} else {
errMessage = `无法连接到服务器!`
}
message.error(errMessage)
}
const handleAuthError = (errno) => {
const authErrMap: any = {
'10031': '登录失效,需要重新登录', // token 失效
'10032': '您太久没登录,请重新登录~', // token 过期
'10033': '账户未绑定角色,请联系管理员绑定角色',
'10034': '该用户未注册,请联系管理员注册用户',
'10035': 'code 无法获取对应第三方平台用户',
'10036': '该账户未关联员工,请联系管理员做关联',
'10037': '账号已无效',
'10038': '账号未找到',
}
if (authErrMap.hasOwnProperty(errno)) {
message.error(authErrMap[errno])
// 授权错误,登出账户
logout()
return false
}
return true
}
const handleGeneralError = (errno, errmsg) => {
if (err.errno !== '0') {
meessage.error(err.errmsg)
return false
}
return true
}
通用操作封装: src/api/server.ts
import axios from 'axios'
import { message } from 'antd'
import {
handleChangeRequestHeader,
handleConfigureAuth,
handleAuthError,
handleGeneralError,
handleNetworkError
} from './tools'
type Fn = (data: FcResponse<any>) => unknown
interface IAnyObj {
[index: string]: unknown
}
interface FcResponse<T> {
errno: string
errmsg: string
data: T
}
axios.interceptors.request.use((config) => {
config = handleChangeRequestHeader(config)
config = handleConfigureAuth(config)
return config
})
axios.interceptors.response.use(
(response) => {
if (response.status !== 200) return Promise.reject(response.data)
handleAuthError(response.data.errno)
handleGeneralError(response.data.errno, response.data.errmsg)
return response
},
(err) => {
handleNetworkError(err.response.status)
Promise.reject(err.response)
}
)
export const Get = <T,>(url: string, params: IAnyObj = {}, clearFn?: Fn): Promise<[any, FcResponse<T> | undefined]> =>
new Promise((resolve) => {
axios
.get(url, { params })
.then((result) => {
let res: FcResponse<T>
if (clearFn !== undefined) {
res = clearFn(result.data) as unknown as FcResponse<T>
} else {
res = result.data as FcResponse<T>
}
resolve([null, res as FcResponse<T>])
})
.catch((err) => {
resolve([err, undefined])
})
})
export const Post = <T,>(url: string, data: IAnyObj, params: IAnyObj = {}): Promise<[any, FcResponse<T> | undefined]> => {
return new Promise((resolve) => {
axios
.post(url, data, { params })
.then((result) => {
resolve([null, result.data as FcResponse<T>])
})
.catch((err) => {
resolve([err, undefined])
})
})
}
统一调用点: src/api/index.ts
import { userApi } from "./path/user"
import { shoporderApi } from "./path/shoporder"
export const api = {
...userApi,
...shoporderApi
}
接口: src/api/path/user.ts
| src/api/path/shoporder.ts
import { Get } from "../server"
export function getUserInfo(id) { ... }
export function getUserName(id) { ... }
export const userApi = {
getUserInfo,
getUserName
}
import { Get } from "../server"
function getShoporderDetail() { ... }
function getShoporderList() { ... }
export const shoporderApi = {
getShoporderDetail,
getShoporderList
}
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/ojNdq-51qCFPX8_8chrpLw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。