likes
comments
collection
share

微信小程序 wx.request 封装(构建-请求拦截、响应拦截)

作者站长头像
站长
· 阅读数 20

一个优雅的前端,搬的每一块砖都必须是优雅,美观的。那我们就先从请求封装开始吧! (如果有使用 uni的小伙伴,一样可以使用,把 wx. 替换成 uni. 就可以了)

1.定义一个深度拷贝的方法,用来参数的合并 放在help模块文件中

// JS对象深度合并
export function deepMerge( target = {}, source = {} ) {
	target = deepClone( target );
	if ( typeof target !== 'object' || typeof source !== 'object' ) return false;
	for ( const prop in source ) {
		if ( !source.hasOwnProperty( prop ) ) continue;
		if ( prop in target ) {
			if ( typeof target[ prop ] !== 'object' ) {
				target[ prop ] = source[ prop ];
			} else {
				if ( typeof source[ prop ] !== 'object' ) {
					target[ prop ] = source[ prop ];
				} else {
					if ( target[ prop ].concat && source[ prop ].concat ) {
						target[ prop ] = target[ prop ].concat( source[ prop ] );
					} else {
						target[ prop ] = deepMerge( target[ prop ], source[ prop ] );
					}
				}
			}
		} else {
			target[ prop ] = source[ prop ];
		}
	}
	return target;
}

2.定义一个url的正则校验方法 放在validate模块文件中

/**
 * 验证URL格式
 */
export function urlRegExp( value ) {
	return /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-.\/?%&=]*)?/.test( value )
}

3.请求封装的代码

import {
	deepMerge
} from './help.js'

import {
	urlRegExp
} from './validate.js'

class Http {
	constructor( ) {
		this.config = {
			// 请求的根域名
			baseUrl: '',
			// 默认的请求头
			header: {},
			method: 'POST',
			// 设置为json,返回后wx.request会对数据进行一次JSON.parse
			dataType: 'json',
			// 此参数无需处理
			responseType: 'text',
			// 默认请求是否开启loading
			loading: true,
		}

		// 拦截器
		this.interceptor = {
			// 请求前的拦截
			request: null,
			// 请求后的拦截
			response: null
		}

		// get请求
		this.get = ( url, data = {}, loading = this.config.loading, header = {} ) => {
			return this.request( {
				method: 'GET',
				url,
				header,
				data
			}, loading )
		}

		// post请求
		this.post = ( url, data = {}, loading = this.config.loading, header = {} ) => {
			return this.request( {
				url,
				method: 'POST',
				header,
				data
			}, loading )
		}

		// put请求
		this.put = ( url, data = {}, loading = this.config.loading, header = {} ) => {
			return this.request( {
				url,
				method: 'PUT',
				header,
				data
			}, loading )
		}

		// delete请求
		this.delete = ( url, data = {}, loading = this.config.loading, header = {} ) => {
			return this.request( {
				url,
				method: 'DELETE',
				header,
				data
			}, loading )
		}
	}

	// 设置全局默认配置
	create( customConfig ) {
		// 深度合并对象,否则会造成对象深层属性丢失
		this.config = deepMerge( this.config, customConfig );
	}

	// 主要请求部分
	request( options = {}, loading = this.config.loading ) {
		options.loading = loading
		// 检查请求拦截
		if ( this.interceptor.request && typeof this.interceptor.request === 'function' ) {
			let tmpConfig = {};
			let interceptorRequest = this.interceptor.request( options );
			if ( interceptorRequest === false ) {
				// 返回一个处于pending状态中的Promise,来取消原promise,避免进入then()回调
				return new Promise( ( ) => {} );
			}
			this.options = interceptorRequest;
		}
		options.dataType = options.dataType || this.config.dataType;
		options.responseType = options.responseType || this.config.responseType;
		options.url = options.url || '';
		options.params = options.params || {};
		options.header = Object.assign( this.config.header, options.header );
		options.method = options.method || this.config.method;

		return new Promise( ( resolve, reject ) => {
			options.complete = ( response ) => {
				response.loading = loading
				// 判断是否存在拦截器
				if ( this.interceptor.response && typeof this.interceptor.response === 'function' ) {
					let resInterceptors = this.interceptor.response( response );
					// 如果拦截器不返回false,直接接入then回调
					if ( resInterceptors !== false ) {
						resolve( resInterceptors );
					} else {
						// 如果拦截器返回false,意味着拦截器定义者认为返回有问题,直接接入catch回调
						reject( response.data || response );
					}
				} else {
					// 如果要求返回原始数据,就算没有拦截器,也返回最原始的数据
					resolve( response );
				}
			}
			// 判断用户传递的URL是否/开头,如果不是,加上/,
			options.url = urlRegExp( options.url ) ? options.url : ( this.config.baseUrl + ( options.url
				.indexOf( '/' ) == 0 ?
				options.url : '/' + options.url ) );

			wx.request( options );
		} )
	}
}
export default Http

4.使用demo:这里我们新建一个request.js文件

将刚刚封装好的http文件引入,利用请求响应拦截器做一些请求的配置

const ACCESS_TOKEN = 'AurhToken' // token凭证的key

import HTTP from './http.js'
// 创建配置信息
const requestConfig = {
	baseUrl: '', // https://test.request.api
	timeout: 10 * 1000, // 请求超时时间
}
// 初始化请求实例
const newHttp = new HTTP( )
newHttp.create( requestConfig )

// 请求拦截配置项
const LoadingDelayTime = 750 // showLoading 延迟时间
let requestNum = 0 // 请求次数
let showLoading = false // loading 状态
let loadingTimer = null // showLoading 定时器
let RedirectTimer = null // 重新登录 定时器

// 请求拦截器
newHttp.interceptor.request = config => {
	// 添加loading
	if ( config.loading ) {
		requestNum++
		// 请求队列中,是第一个请求时,创建loading
		if (requestNum === 1) {
			loadingTimer = setTimeout(() => {
				showLoading = true
				uni.showLoading({
					title: 'loading...',
					mask: true
				})
			}, LoadingDelayTime)
		}
	}
        
	// 添加 Token 凭证
	if ( typeof config.header !== 'object' ) config.header = {}
	config.header[ ACCESS_TOKEN ] = 'This is a token content'
        // 这里可以自定义统一处理一下 请求的参数 
	// config.data = buildOptions( config.data )
        
	return config
}
// 响应拦截器
newHttp.interceptor.response = response => {
	// 关闭 Loading
	if ( response.loading ) {
		requestNum--
		if ( requestNum === 0 ) {
                       if ( loadingTimer ) {
                            clearTimeout( loadingTimer )
                            loadingTimer = null
                        }
			if ( showLoading ) {
                            showLoading = false
                            wx.hideLoading( )
			}
		}
	}
	// 错误统一处理
	if ( response.statusCode === 200 ) {
		const {
			code,
			message
		} = response.data
		switch ( code ) {
			case 0: // 成功响应
				return response.data
				break;
			case 401: // 登录凭证过期 重新登录
                                // 这里做一个定时器防抖,防止多个请求返回401,重复执行
				if ( RedirectTimer ) clearTimeout( RedirectTimer )
				RedirectTimer = null
				RedirectTimer = setTimeout( ( ) => {
					wx.showToast( {
						title: `请先登录`,
						icon: 'none',
						duration: 1500
					} )
					let timerS = setTimeout( ( ) => {
						clearTimeout( timerS )
						// 这里做退出登录的操作
                                                // ......
					}, 1500 )
				}, 2000 )
				return false
				break;
			default:
				wx.showToast( {
					title: message || '网络错误1',
					icon: 'none'
				} )
				return false
		}
	} else {
		wx.showToast( {
			title: '网络错误2',
			icon: 'none',
			duration: 2000
		} )
		return false
	}
}


//GET请求
export function requestGet( {
	url,
	data = {},
	loading = true,
	header = {}
} ) {
	return newHttp.get( url, data, loading, header )
}
//POST请求
export function requestPost( {
	url,
	data = {},
	loading = true,
	header = {}
} ) {
	return newHttp.post( url, data, loading, header )
}
// PUT请求
export function requestPut( {
	url,
	data = {},
	loading = true,
	header = {}
} ) {
	return newHttp.put( url, data, loading, header )
}
// DELETE请求
export function requestDelete( {
	url,
	data = {},
	loading = true,
	header = {}
} ) {
	return newHttp.delete( url, data, loading, header )
}

tips

最后就是在小程序中各个模块中的使用了,在app.js中引入我们封装好的请求方法,最后抛出就ok了。

附app.js示例代码:

import { requestGet,requestPost,requestPut,requestDelete } from './utils/request.js'

App({

    $Http: {
        get: requestGet,
        post: requestPost,
        put: requestPut,
        delete: requestDelete
    }
    
})

附请求示例代码:

// pages/home/home.js
const App = getApp()

page({
    onLoad() {
        this.getDetail()
    },
    getDetail() {
        const params = {
                // 请求api
                url: '/api/detail/get',
                // 请求参数
                data: {
                    id: 123,
                    type: 1
                },
                // 是否开启loading,可选 默认 true
                loading: true 
        }
        App.$Http.post(params).then(res => {
            // 请求成功
            console.log(res)
        })
    }
 })