木灵鱼儿
阅读:226
封装uni.request api为类似axios的用法
前言
uniapp的请求都是通过uni.request
的api实现的,但是这个api和前端常用的axios库用法上有着太多的不同了,移植起来非常痛苦,所以萌生了自己造一个轮子的想法,由于本人技术菜鸡,只能浅浅的仿照一个了。
实际上本人喜欢axios的config的方式调用请求,如下:
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
所以大体上都是围绕这种用法展开工作的。
首先说下使用上的注意事项:
- get请求不支持使用data字段,用了估计也会被忽略,get请求请务必使用params字段传参,参考axios的params
- create方法创建实例的时候,支持的默认配置只有三个字段:
baseURL、timeout、headers
- 为了实现promise返回结果,不允许任何调用的请求配置中存在:
success、fail、complete
回调函数字段,本人通过ts美滋滋的屏蔽了,你用了肯定会报错警告。 - 请求前的拦截器只有成功的拦截器,因为axios中的请求前错误拦截器测试根本没有可以触发的地方,所以直接割了这个。
- 只有响应后的拦截器允许
throw
抛出错误和Promise.reject()
。
基本就这样了,具体可以看看代码,都有注释(优秀作风)
使用
这里是业务层的封装,创建MyAxios实例,配置默认数据baseUrl这些,添加拦截器,请求前对配置对象做特殊处理,请求后解包,报错toast提示。
由于class类的方式,不能将类直接作为函数调用,所以最后提取了api.request
方法单独导出了
import MyAxios, { METHODS } from "./myAxios";
/** 第一层数据类型 */
export interface ResponseData {
code: Number;
data: Object;
msg: string;
}
const api = MyAxios.create({
baseURL: "https://xxx",
timeout: 1000,
});
/** 请求前拦截器 */
api.useRequestInterceptor((config) => {
return config;
});
/** 请求后拦截器 */
api.useResponseInterceptor<ResponseData>(
(res) => {
//如果code不等于1则说明有错误
const { code, msg } = res;
if (code !== 1) {
uni.showToast({
title: `网络请求失败`,
icon: "error",
});
return Promise.reject(new Error(msg));
}
//解包处理
const data = res.data;
return data;
},
(error) => {
uni.showToast({
title: `网络请求失败`,
icon: "error",
});
return error;
}
);
/** 提取request请求函数 */
const request = api.request.bind(api);
export { request, METHODS };
export default api;
api层的使用:
import { request, METHODS } from "@/request";
/** 测试接口 */
export function test() {
return request({
url: "/test",
method: METHODS.GET,
params: {
name: "我是测试接口",
},
});
}
request方法支持泛型,传入一个泛型后就可以推导出本次请求结果的数据类型,方便ts使用了。
import { request, METHODS } from "@/request";
/** 测试接口 */
export function test() {
return request<string>({ //这里的string就是响应拦截器中res.data解包之后的值了
url: "/test",
method: METHODS.GET,
params: {
name: "我是测试接口",
},
});
}
具体的业务使用:
import { test } from "@/apis/test";
test()
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
});
MyAxios封装
/** 请求方法常量 */
export enum METHODS {
"OPTIONS" = "OPTIONS",
"GET" = "GET",
"HEAD" = "HEAD",
"POST" = "POST",
"PUT" = "PUT",
"DELETE" = "DELETE",
"TRACE" = "TRACE",
"CONNECT" = "CONNECT",
}
/** 头信息 */
export interface Headers {
[key: string]: string | number | boolean | undefined | null;
}
/** 实例化的配置 */
export interface AxiosConfig {
baseURL?: string;
timeout?: number;
headers?: Headers;
}
/** 默认配置 */
const defaultConfig: AxiosConfig = {
baseURL: "",
timeout: 60000,
headers: {},
};
/** 定制的请求参数 */
export interface RequestOptions extends Omit<UniNamespace.RequestOptions, "success" | "fail" | "complete"> {}
/** 已知请求类型的配置 */
export interface KnownConfig extends Omit<RequestOptions, "method"> {}
/** get请求类型配置 */
export interface GetConfig extends Omit<KnownConfig, "data"> {
params?: Record<string, any>;
}
/** post请求类型配置 */
export interface PostConfig extends KnownConfig {
params?: Record<string, any>;
}
/** request请求类型配置 */
export interface RequestConfig extends RequestOptions {
params?: Record<string, any>;
}
/** 错误对象 */
export interface AxiosError extends Error {
options?: RequestOptions;
}
/** 请求前拦截器 */
export type RequestInterceptorFn = (config: RequestOptions) => RequestOptions;
export type RequestInterceptorArray = Array<RequestInterceptorFn>;
/** 请求成功拦截器 */
export type ResponseSuccessInterceptorFn<T = any> = (result: T) => any;
export type ResponseSuccessInterceptorArray<T> = Array<ResponseSuccessInterceptorFn<T>>;
/** 请求失败拦截器 */
export type ResponseInterceptorFailFn = (error: AxiosError | Error) => AxiosError | Error;
export type ResponseInterceptorFailArray = Array<ResponseInterceptorFailFn>;
class Axios {
/** createOptions */
private createOptions: AxiosConfig;
/** 取消请求函数存储器 */
private cancelTokenMap: Map<string, Function> = new Map();
/** 请求前拦截器 */
private requestInterceptors: RequestInterceptorArray = [];
/** 请求后成功的拦截器 */
private responseSuccessInterceptors: ResponseSuccessInterceptorArray<any> = [];
/** 请求后失败的拦截器 */
private responseFailInterceptors: ResponseInterceptorFailArray = [];
constructor(options: AxiosConfig = defaultConfig) {
const { baseURL, timeout, headers } = options;
this.createOptions = {
baseURL: baseURL ?? defaultConfig.baseURL,
timeout: timeout ?? defaultConfig.timeout,
headers: headers ?? defaultConfig.headers,
};
}
/** 静态创建实例方法 */
public static create(options?: AxiosConfig): Axios {
return new Axios(options);
}
/** get请求 */
public get<T = any>(options: GetConfig): Promise<T> {
const getOptions: KnownConfig = { ...options };
if (options.params) {
getOptions.data = options.params;
Reflect.deleteProperty(getOptions, "params");
}
const requestConfig = this.mergeDefaultConfig(getOptions);
return this._request<T>({ method: METHODS.GET, ...requestConfig });
}
/** post请求 */
public post<T = any>(options: PostConfig): Promise<T> {
let postOptions: KnownConfig = { ...options };
//生成参数字符串
const params = options.params;
let paramsStr = "";
if (params) {
Object.keys(params)
.sort((a, b) => a.localeCompare(b))
.forEach((key) => {
paramsStr += `${key}=${params[key]}&`;
});
paramsStr = paramsStr.replace(/&$/, "");
}
//拼接参数字符串
if (postOptions.url.includes("?")) {
postOptions.url += `&${paramsStr}`;
} else {
postOptions.url += `?${paramsStr}`;
}
const requestConfig = this.mergeDefaultConfig(postOptions);
return this._request<T>({ method: METHODS.POST, ...requestConfig });
}
/** 自动判断get和post请求的请求方法 */
public request<T = any>(options: RequestConfig): Promise<T> {
let { method } = options;
if (!method) method = METHODS.GET;
switch (method) {
case METHODS.GET:
return this.get<T>(options);
case METHODS.POST:
return this.post<T>(options);
default:
return Promise.reject(new Error("不支持的请求方法"));
}
}
/** 获取uni的请求并类型转换为Promise */
private _request<T = any>(options: RequestConfig): Promise<T> {
return new Promise<T>((resolve, reject) => {
//请求前拦截器
let requestConfig = options;
this.requestInterceptors.forEach((interceptor) => {
requestConfig = interceptor(requestConfig);
});
const requestTask = uni.request({
...requestConfig,
success: async (res) => {
try {
const { statusCode } = res;
//错误处理
if (statusCode !== 200) {
let msg = "请求失败";
const data = res.data as { msg: string };
if (typeof data.msg === "string") msg = data.msg;
let error: AxiosError = this.createError(msg, options);
//删除取消函数
this.cancelTokenMap.delete(options.url);
//请求失败拦截器
for (let i = 0, len = this.responseFailInterceptors.length; i < len; i++) {
error = this.responseFailInterceptors[i](error);
}
return reject(error);
}
//删除取消函数
this.cancelTokenMap.delete(options.url);
//请求成功拦截器
let resultData = res.data;
for (let i = 0, len = this.responseSuccessInterceptors.length; i < len; i++) {
resultData = await this.responseSuccessInterceptors[i](resultData);
}
return resolve(resultData as T);
} catch (err: any) {
let msg = "请求失败";
if (typeof err.errMsg === "string") msg = err.errMsg;
const error: AxiosError = this.createError(msg, options);
//删除取消函数
this.cancelTokenMap.delete(options.url);
//请求失败拦截器
for (let i = 0, len = this.responseFailInterceptors.length; i < len; i++) {
err = this.responseFailInterceptors[i](err);
}
return reject(err);
}
},
fail: (err) => {
let msg = "请求失败";
if (typeof err.errMsg === "string") msg = err.errMsg;
let error: AxiosError = this.createError(msg, options);
//删除取消函数
this.cancelTokenMap.delete(options.url);
//请求失败拦截器
for (let i = 0, len = this.responseFailInterceptors.length; i < len; i++) {
error = this.responseFailInterceptors[i](error);
}
return reject(error);
},
});
//存储取消函数
this.cancelTokenMap.set(options.url, requestTask.abort.bind(requestTask));
});
}
/** 获取用户配置和默认配置的合并后的配置对象 */
private mergeDefaultConfig(options: KnownConfig): RequestOptions {
const { baseURL, timeout, headers } = this.createOptions;
const url = `${baseURL}${options.url ?? ""}`;
const mergeOptions = { timeout, ...options };
Reflect.deleteProperty(mergeOptions, "url");
mergeOptions.url = url;
const oh = options.header;
if (oh) {
mergeOptions.header = { ...headers, ...oh };
} else {
mergeOptions.header = headers || {};
}
return mergeOptions;
}
/** 创建错误对象 */
private createError(msg: string, options: RequestOptions): AxiosError {
const error: AxiosError = new Error(msg);
error.options = options;
return error;
}
/** 通过url获取取消请求函数 */
public getRequestAbort(url: string): Function | undefined {
return this.cancelTokenMap.get(url);
}
/** 注册请求前的拦截器 */
public useRequestInterceptor(fn: RequestInterceptorFn): void {
this.requestInterceptors.push(fn);
}
/** 注册请求后的拦截器 */
public useResponseInterceptor<T>(successFn: ResponseSuccessInterceptorFn<T>, failFn?: ResponseInterceptorFailFn): void {
this.responseSuccessInterceptors.push(successFn);
failFn && this.responseFailInterceptors.push(failFn);
}
}
export default Axios;
取消请求
其实我把取消请求功能也加上了,通过实例api.getRequestAbort(url)
方法,但是个人感觉不是很好使,这个url是一个完整链接哦,是包含baseUrl和链接参数的,因为实在想不到啥好办法去区分了。
如果你需要的话,可以自行使用,getRequestAbort
得到的就是abort
取消请求函数,运行它即可。
需要注意的是,如果这个请求已经完成了,success或者fail,那个getRequestAbort就获取不到了,因为我把abort
清理了。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
nuxt 一文读懂@nuxtjs/axios的用法
在nuxt中,有一个专门的axios版本:@nuxtjs/axios;但是由于nuxt的文档,或者说是资料不是很齐全(零散),初步了解后觉得这个插件并不是很方便,很多时候,我们都是模块化的axios写法,在使用@nuxtjs/axios时,会发现有点束手束脚,于是转而放弃使用,而是单独引入axios,然后一顿操作。当我们单独封装axios的时候,有没有这种想法:报错了能不能跳转到nuxt的错误页面?;但是实现时发现,除了在asyncData这些方法里面可以解构到error对象然后利用它跳转,好像在单独的一个js文件里面,没办法直接跳转。但是,如果我们使用@nuxtjs/axios,那么我...

koa教程2 HttpBearerAuth 传递token
前端发送token到后端,一般有三种方式:链接中携带token,query参数post这种请求body中携带token头信息携带token但是事实上http已经为我们提供了传token的方式,是一个相对来说比较安全的方式,就是HttpBearerAuth 其实他也是一个头信息来着,只是相对来说多了一点操作,在apipost软件中,我们可以直接可视化设置如果是axios,则是需要添加一个头信息:axios({ method: 'get', url: url, responseType: 'blob', headers: { 'Authoriz...

nuxt 中axios反代的用法
具体的反代我其实在之前《nuxt 入坑的那些事》就说过了,这里就不多说,主要是讲下我们自己axios怎么处理反代。在nuxt中我们其实可以自己封装一个axios模块来使用,也就是常见的后台管理项目中对aixos模块化的用法。常常就是import引入axios,然后使用拦截器进行加工处理,然后在导出,使用的时候import这个axios。import axios from "axios"; let api = axios.create({ baseURL: xxxxxx, timeout: 15000, }); //请求前 api.interceptors.r...
vue项目中关于axios取消请求的解决方案
在axios速读中,我们了解到axios请求的取消方法。const CancelToken = axios.CancelToken; //请求前拦截器 axios.interceptors.request.use(function(config) { //添加取消token const source = CancelToken.source(); config.cancelToken = source.token; //取消请求 source.cancel("取消请求"); return config; }, fu...
axios 速读
快速了解axios常用功能,适用于对axios有一定使用经验的人。常用请求方法的参数axios.get(url, config);get方法支持两个参数,第一个是url,第二个是{}键值对配置对象。axios.post(url, data, config);post方法支持三个参数:请求地址,上传的数据{},{}键值对配置对象。当然还有一些其他请求类型,可以查看官方文档,这两种常用。除了.xxx方法使用协议,还可以直接传入配置对象的形式使用。axios({ method:'get', url:'xxxx', })这种方式比较适合封装使用。适用度广泛一些。一些常用的config配置...

vue路由切换取消上一个路由未完成的axios请求
同事写的,我也是借花献佛,顺带做个笔记axios拦截器添加取消请求的方法因为我们需要在路由进入前判断有没有存在取消请求的方法,所以需要一个存储介质,选用数组,然后存放在vuex中,一是方便动态追踪,二是本来就在axios和router里面import了vuex,就没必要添加window的全局变量了。import axios from "axios"; import store from "./store"; //vuex //拦截器 api.interceptors.request.use(config => { //设置取消请求的...
如何取消axios请求以及取消后错误处理的思路
前端在api请求的时候,有时候可能会遇到发送多个请求的时候,但是实际有效的是最后一次,但是后端响应的值不一定是你最后一次发送的请求,有可能最后一次比之前的还要更快响应,这就导致用户切换到a数据,前端显示是b数据。解决这个情况有很多种方式,比如改动api请求方式,或者请求如果重复同一个接口,取消前面的请求,发起新的请求。这里我们是后者。使用方式官方提供了两个例子,第一个例子照着写怎么都没用,第二个没看懂他的参数到底是data的还是谁的,最终还是百度了好多文章,才渐渐明白。首先,我的项目使用的axios是二次封装的,也就是通过axios.create()方法预设了baseURL和拦截器的,最...

精华 axios 修改http协议和转formData对象
修改协议最近发现axios的修改协议有个很方便的操作,就是通过create创建一个axios对象的时候,就可以直接设置上传协议了。const api = axios.create({ headers: { 'Content-Type': 'application/x-www-form-urlencoded', } }); 这样我们将协议改为了application/x-www-form-urlencoded这个协议可以上传formData对象,但是他不能上传文件,所以如果你要上传文件,需要改为这个协议:multipart/form-data如果默认配置无法满足,你可...
axios error对象解构
const {response:{status,statusText}} = err;status为状态码statusText为错误信息
axios 拦截器 登录超时或失效,删除token并跳转Login页面,并且记住跳转前path,登录成功后跳转回来
最近遇到如题的一个需求,因为很常用,所以我把它记录下来,方便以后使用。流程用户正常使用时,或者后台刷新了缓存,导致登录超时或者失效,前端api请求得到一个信息,比如code=10010,那么就表示用户登录信息失效了,前端页面就需要提示用户登录失效了,需要重新登录。用户点击确认后,我们删除本地token,然后路由跳转。跳转之前我们需要记录当前的路由,这样用户登录成功后可以跳转回来,体验极好。233...流程示意:登录失效 --> 前端api请求时得到code=10010 --> 弹出提示窗口 --> 用户点击 重新登录 --> 清除token --> ...