微信小程序 — request请求封装

241 阅读6分钟

新建wxRequest.js文件

创建WxRequest类:通过类的方式来进行封装,会让代码更加具备复用性,也可以方便添加新的属性和方法

主要内容:

  • 1、封装request请求方法,成功与失败的回调;
  • 2、设置请求参数
    • 2.1 默认参数:在WxRequest类中添加defaults 实例属性设置默认值
    • 2.2 实例化时参数:实例化时传入参数,需要在constructor构造函数形参进行接收
    • 2.3 调用实例方法时传入
  • 3、在request的基础上 封装请求快捷方法 get、delete、post、put实例方法;
  • 4、定义请求拦截器/响应拦截器 统一处理成功和失败;
  • 5、增加权限校验,再拦截器中判断是否有token访问令牌,添加到请求头;
  • 6、添加并发请求Promise.all;
    • 7、添加loading效果,注意并发场景loading效果 通过队列解;
    • 7.1 在类型新增queue实例属性,初始值为空数组;
    • 7.2 发请求前判断queue为空,则显示loading,然后立即向queue新增请求标识;
    • 7.3 在complete回调中每次请求结束,从queue中移除一个请求标识,queue为空时,隐藏loading;
    • 7.4 为了解决网络请求过快loading产生闪烁问题,可以使用定时器来做判断;
    • 7.5 控制接口是否需要显示loading,实现可配置。showLoading。
  • 8、封装uploadFile实例方法
class WxRequest {
	// 实例属性定义在构造函数上方

	// 定义实例属性,用来设置默认参数对象
	defaults = {
		baseURL: '', // 请求基准地址
		url: '', // 开发者服务器接口地址
		data: null, // 请求参数
		method: 'GET', // 默认请求方法
		// 设置请求头
		header: {
			'Content-type': 'application/json', // 设置数据交互格式
		},
		timeout: 60000, // 小程序默认超时时间 60000 一分钟
		showLoading: true, // 控制是否使用默认的loading
	};

	// 定义拦截器对象
	// 在构造函数上方 定义interceptors实例属性,属性包含request、以及response方法
	interceptors = {
		// 默认的拦截器 会被实例配置的拦截器覆盖
		// 请求拦截器 在 请求发送之前(request前面) 对请求参数进行新增或修稿
		request: (config) => config,

		// 响应拦截器 在 服务器响应以后,对服务器响应的数据进行逻辑处理
		response: (response) => response
	};

	// 定义数组队列,初始值是一个空数组,用来存储请求标识
	queue = [];

	/**
	 * 定义contructor构造函数,用于创建和初始化类的属性及方法
	 * @param 用户传入的请求配置项 默认值{}
	 */
	constructor(params = {}) {
		// params:实例化时传入的参数能够被constructor进行接收

		// 使用Object.assign 合并默认参数以及传递请求参数
		// 注意:需要传入的参数,副覆盖默认的参数,则传入的参数要放在最后
		this.defaults = Object.assign({}, this.defaults, params)

	};

	// request实例方法接收一个对象类型的参数options
	// options属性值要和wx.request方法调用时传递的参数保持一致
	request(options) {
		// 如果有新的请求进来就清除上一次的定时器
		this.timeId && clearTimeout(this.timeId)

		// 拼接完整的url
		options.url = this.defaults.baseURL + options.url;

		// 合并请求参数
		options = {
			...this.defaults,
			...options
		};

		if (options.showLoading && options.method !== 'UPLOAD') {
			// 在请求发送之前添加loading效果 wx.showLoading()
			// 判断queue队列是否为空 为空就调用wx.showloading(),不为空就不再调用wx.showLoading()
			this.queue.length === 0 && wx.showLoading();

			// 然后立刻向 queue 队列中添加一个自定义的请求标识 每一个标识代表一个请求,
			this.queue.push('request');
		}

		// 在请求发送之前调用请求拦截器 新增和修改请求参数
		options = this.interceptors.request(options);

		// 需要使用Promise封装wx.request(),处理异步请求
		return new Promise((resolve, reject) => {
			if (options.method === 'UPLOAD') {
				wx.uploadFile({
					...options,
					success: (res) => {
						// 需要将返回的数据通过JSON.parse转换成对象
						res.data = JSON.parse(res.data);
						// 合并参数
						const mergeRes = Object.assign({}, res, {
							config: options,
							isSuccess: true
						});
						resolve(this.interceptors.response(mergeRes));
					},
					fail: (err) => {
						const mergeErr = Object.assign({}, err, {
							config: options,
							isSuccess: false
						});
						reject(this.interceptors.response(mergeErr));
					}
				})
			} else {
				wx.request({
					...options,
					// 接口调用成功时会触发success回调函数
					// 使用wx.request请求的时候,只要接收到服务器返回的结果,无论statusCode、状态码是多少,都会走success
					// 404、401等都走success,开发时需要根据业务逻辑判断
					success: (res) => {

						// 不论成功还是失败都调用响应拦截器  拦截器接收服务器响应的数据
						// 对数据进行逻辑处理 处理后再返回,通过resolve抛出去
						// 给响应拦截器传递参数时,最好把请求参数一并传递,方便代码调试或其他逻辑处理
						// 需要先合并参数,然后将合并的参数传递给响应拦截器

						// 可以追加一个isSuccess字段表示是否请求成功,因为无论success还是fail,都会被响应拦截器拦截
						const mergeRes = Object.assign({}, res, {
							config: options,
							isSuccess: true
						})

						resolve(this.interceptors.response(mergeRes));
					},
					// 当接口调用失败时会触发fail回调函数
					// 一般只有在 网络出现异常,请求超时等时候才会走fail
					fail: (err) => {
						const mergeRes = Object.assign({}, err, {
							config: options,
							isSuccess: false
						})
						reject(this.interceptors.response(mergeRes));
					},
					//  请求成功失败都会执行
					complete: () => {
						if (options.showLoading) {
							// 无论成功失败,都要隐藏loading,在请求结束后,都需要从queue中删除一个标识
							this.queue.pop();
							this.queue.length === 0 && this.queue.push('request');
							this.timeId = setTimeout(() => {
								//定时器执行的时说明没有执行request一开始的clearTimeout,
								// 也就是说,此时没有新的请求进来了,就可以把最后一个request标识删除
								this.queue.pop();
								// 删除一个标识后需要判断queue是否为空,如果为空,说明并发请求都完成了
								// 就需要隐藏loading 要调用wx.showLoading
								this.queue.length === 0 && wx.hideLoading();
								clearTimeout(this.timeId)
							}, 100)
						}
					}
				})
			}
		})
	}

	/**
	 * 封装 get 请求方法
	 * @param { string } url 接口地址
	 * @param {{}} [data={}] 请求参数
	 * @param {{}} [config={}] 请求配置
	 */
	get(url, data = {}, config = {}) {
		// 调用request发送请求,组织参数传递给request 将request方法的返回值return出去
		return this.request(Object.assign({
			url,
			data,
			method: 'GET'
		}, config))
	}
	post(url, data = {}, config = {}) {
		return this.request(Object.assign({
			url,
			data,
			method: 'POST'
		}, config))
	}
	delete(url, data = {}, config = {}) {
		return this.request(Object.assign({
			url,
			data,
			method: 'DELETE'
		}, config))
	}
	put(url, data = {}, config = {}) {
		return this.request(Object.assign({
			url,
			data,
			method: 'PUT'
		}, config))
	}
	/**
	 * 处理并发请求
	 * @param {...*} promise promise数组
	 * const resList = await instance.all(request1(),request2(),request2())
	 */
	all(...promise) {
		// 使用展开运算符接收传递的参数,会将传入的参数转成数组
		return Promise.all(parmise)
	}

	/**
	 * 文件上传接口封装
	 * @param { string } url 文件上传地址
	 * @param {string } filePath 要上传的文件资源路径
	 * @param {string } name 文件对应的key
	 * @param {{}} [config={}] 其他配置项
	 */
	upload(url, filePath, name = 'file', config = {}) {
		return this.request(
			Object.assign({
				url,
				filePath,
				name,
				method: 'UPLOAD'
			}, config)
		)
	}
}

export default WxRequest;

新建http.js文件

对 WxRequest进行 实例化

import WxRequest from './wxRequest.js'

// 对 WxRequest进行 实例化 参数在WxRequest的constructor中接收
const instance = new WxRequest({
	baseURL: 'https://tea.qingnian8.com/api',
	timeout: 10000,
});

// 配置请求拦截器
instance.interceptors.request = (config) => {
	// 在请求发送之前做些什么 此处会覆盖实例方法中的拦截器操作
	// 在发送请求之前需要先判断本地是否存在访问令牌token,如过存在就添加到header中
	const token = wx.getStorageSync('token');
	if (token) {
		config.header['token'] = token;
	}
	return config;
}
instance.interceptors.response = async (response) => {
	// 对服务器响应的数据做些什么
	const {
		isSuccess,
		data
	} = response;

	if (!isSuccess) {
		// 请求走的是fail
		wx.showToast({
			title: '网络异常请重试',
			icon: 'error'
		})
		return response;
	}

	// 响应成功 走的是success 直接返回响应的data
	// 需要根据后端返回的业务状态码判断处理 如:200 404 401等
	switch (data.code) {
		case 200:
			return data;
			break;
		case 401:
			const res = await wx.showModal({
				title: '提示',
				content: '鉴权失败,请重新登录',
				success(res) {
					if (res.confirm) {
						// 清楚失效的token 及本地存储的全部信息
						wx.clearStorage();
						// 跳到登录页
						wx.navigateTo({
							url: '/pages/login/login'
						})
						return Promise.reject(response);
					} else if (res.cancel) {
						console.log('用户点击取消 不做操作')
					}
				}
			})
			break;
		default:
			wx.showToast({
				title: '程序出现异常 请重试!'
			})
			return Promise.reject(response);
			break;
	}
	return data;
}
// 将实例导出
export default instance;

新建页面index.js

测试封装的wxRequest请求示例

import instance from '@/utils/http.js'

const getData1 = () => {
  instance.request({
    url: '/test/banner',
    method: 'GET'
  }).then(res => {
    console.log(res);
  })
}

const getData2 = async () => {
  const resList = await instance.all(
    instance.get('/test/banner'),
    instance.get('/test/newList'),
    instance.get('/test/classify'),
    instance.get('/test/wallList'),
  )
}

const getData3 = async () => {
  const res = await instance.get('/test/banner', null, {
    showLoading: false
  })
}

说明

学习地址:www.bilibili.com/video/BV1LF…