uniapp请求接口封装
在uniapp
发送请求跟web
的不同,而且通过uni.request
这个方法进行调用。
示例:
js
uni.request({
url: 'https://www.example.com/request', //仅为示例,并非真实接口地址。
data: {
text: 'uni.request'
},
header: {
'custom-header': 'hello' //自定义请求头信息
},
success: (res) => {
console.log(res.data);
this.text = 'request success';
}
});
可能对于习惯了使用axios
的开发者而言,会不太喜欢使用这种方式。因此文章讲介绍一下怎么在uniapp
上实现一个类似axios
的请求方式。
定义构造函数
在这里命名为UniAxios
js
class UniAxios {
constructor(options = {}) {
this.defaults = options;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
}
在UniAxios
构造函数里创建了两个拦截器管理实例。
同时添加了拦截器处理
拦截器
拦截器管理机制其实很简单。就只有一个handlers
属性(用于保存拦截器)及三个原型方法(添加、移除、执行)
js
class InterceptorManager {
constructor() {
this.handlers = [];
}
}
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
// 默认异步
synchronous: options ? options.synchronous : false
});
// 当前拦截器id
return this.handlers.length - 1;
};
InterceptorManager.prototype.remove = function remove(id) {
if (this.handlers[id]) {
// 只移除拦截器引用,不处理数组,否则会影响之前的拦截器索引
this.handlers[id] = null;
}
};
InterceptorManager.prototype.forEach = function forEach(fn) {
this.handlers.forEach((handler) => {
if (handler !== null) {
fn(handler);
}
});
};
处理不同平台请求method
对于不同平台,可能支持的method
不一致,因此这里也需要进行处理。
因为不同的请求方式在uni.request
中只是method
字段的不同,完全可以单独把uni.request
封装好,通过传递method
来实现不同的请求方法。
以微信小程序为例:
js
// 微信小程序支持的请求方法
const supportedMethodType = [
'GET',
'POST',
'PUT',
'DELETE',
'CONNECT',
'HEAD',
'OPTIONS',
'TRACE',
];
// 通过遍历的方式添加到构造函数的原型上
supportedMethodType.forEach((item) => {
LeoAxios.prototype[item.toLocaleLowerCase()] = function(url, data, header, ...args) {
return this.request(
item.toLocaleLowerCase(),
url,
data, {
...this.defaults.header,
...header,
},
...args
);
};
});
上面添加的这些原型方法内部其实都是调用了同一个request
方法。
请求发送
在request
方法中,需要合并上面的不同请求传过来的参数以及初始化UniAxios
时的配置。
同时处理拦截器,将拦截器组装成一个数组用于Promise.then
的链式调用。
js
function processArguments(args) {
if (args.length > 1) {
return {
method: args[0],
url: args[1],
data: args[2],
header: args[3],
rest: args.length > 4 ? args.slice(3) : undefined,
};
}
return args;
}
function mergeConfig(defaults, config) {
return Object.assign(defaults, config);
}
LeoAxios.prototype.request = function request() {
let config = {};
// 兼容传参
config = processArguments(arguments);
// 合并配置
config = mergeConfig(JSON.parse(JSON.stringify(this.defaults)), config);
// 请求拦截器栈
const requestInterceptorChain = [];
// 拦截器是否是同步的
let synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
synchronousRequestInterceptors =
synchronousRequestInterceptors && interceptor.synchronous;
// 将处理方法推进栈中,采用unshift方法
requestInterceptorChain.unshift(
interceptor.fulfilled,
interceptor.rejected
);
});
// 响应拦截器栈
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
// 异步的处理
if (!synchronousRequestInterceptors) {
// 请求方法
let chain = [dispatchEvent, undefined];
// 在请求方法前添加请求拦截器
Array.prototype.unshift.apply(chain, requestInterceptorChain);
// 在请求方法后添加响应拦截器
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
// 请求拦截器
while (requestInterceptorChain.length) {
const onFulfilled = requestInterceptorChain.shift();
const onRejected = requestInterceptorChain.shift();
try {
newConfig = onFulfilled(config);
} catch (err) {
onRejected(err);
break;
}
}
// 发送请求
try {
promise = dispatchEvent(config);
} catch (err) {
return Promise.reject(err);
}
while (responseInterceptorChain.length) {
promise = promise.then(
responseInterceptorChain.shift(),
responseInterceptorChain.shift()
);
}
return promise;
};
dispatchEvent
方法就是用于发送请求。
js
function dispatchEvent(config) {
let timer,
requestTask,
// overtime 请求是否超时
overtime = false;
// timer 检测超时定时器,requestTask 网络请求 task 对象,aborted 请求是否已被取消,abort 取消请求方法
return new Promise((resolve, reject) => {
if (config.cancel) {
return reject({
requestConfig: config,
errMsg: '网络请求失败:主动取消'
});
}
requestTask = uni.request({
url: config.url[0] === '/' ? config.baseUrl + config.url : url,
data: config.data,
method: config.method,
header: config.header,
...config.rest,
success: async function success(res) {
res.requestConfig = config;
if(config.statusCode && config.statusCode.indexOf(res.statusCode) === -1) {
reject(res);
} else {
resolve(res);
}
},
fail: async function fail(err) {
if (overtime) {
return;
}
reject({
...err,
requestConfig: config
});
},
complete: function complete() {
// 清除超时定时器
clearTimeout(timer);
},
});
timer = setTimeout(async () => {
// 会触发fail方法
overtime = true;
requestTask.abort();
reject({
requestConfig: config,
errMsg: '网络请求时间超时'
});
}, config.timeout);
})
}
在这个方法中还添加了超时处理,在超时后调用requestTask.abort
方法。
使用
来看看具体怎么使用:
首先初始化一个实例:
js
const request = new LeoAxios({
baseUrl: 'http://localhost:8081/',
timeout: 60000,
header: {
'Content-Type': 'application/json',
},
statusCode: [200, 304, 401]
});
传入一些基础配置。
之后就可以使用request.post
、request.get
这些方法了。
js
request.get('/test', {}, {
"Content-Type": 'application/x-www-form-urlencoded'
});
request.post('/testpost', {key: 1, value: 2});
拦截器的使用
拦截器有几点需要注意的是:
- 在
fulfilled
方法中需要返回传入的参数,保证下一个拦截器中能获取到传参 - 在
rejected
方法中需要返回一个Promise.reject
,这样才能保证在其他拦截器触发的都是rejected
js
request.interceptors.request.use(
function fulfilled(options) {
return options;
},
function rejected(err) {
return Promise.reject(err);
},
option: {
synchronous: false
}
);