前言
微信小程序开发,网络请求使用小程序内置 api wx.request() 。我们知道微信小程序的方法调用风格基本都是将回调函数作为配置传入方法参数。常用 axios 作为网络请求的同学更希望拥有一个Promise
风格的 api,网上也有一些将微信小程序 api 转为 Promise
的库,但在这里更希望明白实现这样一个功能的步骤,所以我封装了一个简易的服务,供大家参考。
创建 HttpService 服务类
请求的 method 有很多,在类中我们创建了一个工厂方法 __factory()
,用于生成不同类型的请求,并暴露出来。
export class HttpService {
constructor() { }
// method: OPTIONS | GET | HEAD | POST | PUT | DELETE | TRACE | CONNECT
__factory(method) {
return (url, data, config) => {
return new Promise((resolve, reject) => {
wx.request({
...config,
url,
method,
data,
success(res) {
resolve(res);
},
fail(event) {
reject(event);
}
});
})
}
}
get = this.__factory('GET');
post = this.__factory('POST');
del = this.__factory('DELETE');
}
这样一个简单的 Promise 风格 api 就封装出来了,使用如下:
const http = new HttpService();
http.get(url, data, config).then(res => {})
增加拦截器功能
拦截器包括请求拦截器和响应拦截器,请求拦截器在请求发送之前可以对请求参数做修改和转换;响应拦截器包括成功响应(http status 以 2 开头,如:200、201、202等,详细参考 这里)和错误响应(http status 一般以4或5开头,4开头的为客户端错误,5开头的为服务端错误,常见的如:400、401、404、500、503等)。
-
改造构造函数
为构造函数声明一个options
参数,接收服务的统一设置,并对options
参数做解构操作,获取传入的值,没有则定义默认值,最后赋值给私有成员__options
供其他方法使用。options
参数中定义了 wx.request 的全局默认timeout
和拦截器对象interceptor
。
export class HttpService {
__options = {}; // 存储服务配置
constructor(opts = {}) {
const { timeout = 0, interceptor } = opts;
const {
request = cfg => cfg,
response = res => res
} = interceptor || {};
this.__options = {
timeout: timeout || 0,
interceptor: {
request,
response
}
}
}
// ...
}
- 请求拦截
本质就是在请求发出之前对请求的配置做处理,将请求参数传入请求拦截器,请求拦截器执行后返回一个新的参数配置。
const { timeout, interceptor } = this.__options;
const params = interceptor.request({
...config,
url,
method,
data,
timeout,
header: {}
});
wx.request({
...params,
// ... other params
})
- 响应拦截器
我们在wx.request()
参数的success
中做成功响应,fail
中做错误响应,返回对象中增加一个success
字段标识成功还是失败。
wx.request({
// ...
success(res) {
resolve(interceptor.response({
...res,
success: true
}));
},
fail(event) {
reject(interceptor.response({
...event,
success: false
}));
}
});
增加 abort 功能
到目前为止,我们的封装已经已经完全能够使用了,但是有一些应用场景,比如我们发起了一个请求,但是服务端响应比较慢,在这期间如果页面发生了跳转,这时候请求返回就不再需要做响应处理了,所以我们就需要一个终止请求的方法。
参照微信官方文档可以知道,wx.request() 返回一个 RequestTask 对象,此对象的 abort() 方法用于中断请求任务,刚好是我们需要的,但是经过封装后的请求返回的是一个 Promise 对象,不再是 RequestTask 对象,这时候就需要做一些兼容处理了。
对比 axios ,它使用的是一个 CancelToken 工厂实现取消,我们也可以模仿一个,实现代码参考下方的 HttpTask。
完整代码
- 任务管理类
/core/provider/http.task.js
,暴露了abort()
和getTask()
两个方法
export class HttpTask {
static TASKS = {}; // 用于存储 RequestTask
constructor() {
this.id = Math.random().toString(36).substr(2); // 对每个任务生产随机 id
}
// 用于中断/取消请求
abort() {
if (HttpTask.TASKS[this.id]) {
HttpTask.TASKS[this.id].abort();
delete HttpTask.TASKS[this.id];
}
}
// 获取 RequestTask 实例
getTask() {
return HttpTask.TASKS[this.id];
}
}
- 服务类
/core/provider/http.service.js
小程序中 method 支持很多方法,这里我们只暴露了get
、post
、delete
3个方法,如果需要其它方法,可以按照格式添加。
import { HttpTask } from './http.task';
export class HttpService {
__options = {}; // 存储服务配置
constructor(opts = {}) {
const { timeout = 0, interceptor } = opts;
const {
request = cfg => cfg,
response = res => res
} = interceptor || {};
this.__options = {
timeout: timeout || 0,
interceptor: {
request,
response
}
}
}
/**
* 请求方法构造工厂
* @param { OPTIONS | GET | HEAD | POST | PUT | DELETE | TRACE | CONNECT } method
* @return { (url: string; data: any; config: object;) => Promise<any> }
*/
__factory(method) {
return (url, data, config) => {
return new Promise((resolve, reject) => {
const { timeout, interceptor } = this.__options;
const params = interceptor.request({
...config,
url,
method,
data,
timeout,
header: {}
});
// DELETE 请求参数处理
if (method === 'DELETE') {
const params = Object.entries(data).map(([key, val]) => `${key}=${val}`).join('&');
params.url += params.url.includes('?') ? params : `?${params}`;
params.data = undefined;
}
const task = wx.request({
...params,
data: params.data || undefined,
success(res) {
resolve(interceptor.response({
...res,
success: true
}));
},
fail(event) {
reject(interceptor.response({
...event,
success: false
}));
}
});
if (config.taskId) {
HttpTask.TASKS[config.taskId] = task;
}
})
}
}
get = this.__factory('GET');
post = this.__factory('POST');
del = this.__factory('DELETE');
}
使用
- 创建服务实例
/core/service/http.js
,并添加全局参数配置和拦截器配置
import { HttpService } from '../provider/http.service';
export default new HttpService({
timeout: 6000,
interceptor: {
// 请求拦截
request(config) {
config.header.Authorization = wx.getStorageSync('token');
return config;
},
// 响应拦截
response(event) {
// 请求错误处理
if (!event.success) {
wx.showToast({
title: event.errMsg,
icon: 'none'
});
// 如果返回值为空,则请求的 catch 不会触发
return event;
}
// 请求成功处理
const {
statusCode,
data: {
code,
data,
msg = '业务异常',
}
} = event;
switch (statusCode) {
case 200:
switch (code) {
case 200:
return Promise.resolve(data);
default:
wx.showToast({
title: msg,
icon: 'none'
});
}
return Promise.reject(msg);
case 401:
wx.showToast({
title: 'token 已失效,请重新登录!',
icon: 'none'
});
// other code
break;
default:
wx.showToast({
title: `response exception: ${statusCode}`,
icon: 'none'
});
return Promise.reject(event);
}
},
}
})
- 创建业务请求服务类
/service/user.js
import http from './http';
import app from '../config/app';
/**
* 获取用户列表
* @param { object } params 请求参数
* @return { Promise<User[]> }
*/
export async function getUserList(params) {
return http.get(`${app.prefix}/users`, {
page: 1,
rows: 10,
...params
}).then(res => Promise.resolve(res.list));
}
/**
* 获取用户详情
* @param {number} id
* @return { Promise<User> }
*/
export async function getUserById(id) {
return http.get(`${app.prefix}/users/${id}`);
}
- pages 页面中使用
现在的网络速度已经很快了,中断请求的应用场景较少,在开发初期可以不作考虑。
import { getUserById } from '../../core/service/user';
Page({
// ...
onLoad(options) {
getUserById(options.id).then(res => {
console.log('user', res);
});
}
})
- 中断请求
我们先改造一下服务类,方法中增加一个taskId
参数
export async function getUserById(id, taskId) {
return http.get(`${app.prefix}/users/${id}`, {}, { taskId });
}
然后改造页面方法,只需要 new 一个 HttpTask 实例,并将 id 传入请求方法即可,然后就可以直接通过 task.abort()
直接中断请求,或者通过 task.getTask()
获取微信 RequestTask 对象。
import { HttpTask } from '../../core/provider/http.task';
import { getUserById } from '../../core/service/user';
Page({
// ...
onLoad(options) {
const task = new HttpTask();
getUserById(options.id, task.id).then(res => {
console.log('user', res);
});
this.setData({ task });
},
onUnload() {
this.data.task && this.data.task.abort();
}
})
总结
到目前为止,已经能够应付大部分的开发任务了,如果不满足,可以在此基础上自行扩展或留言。