基于 wx.request 封装类似axios请求库
前言
在H5项目中,我们可以利用axios来发送ajax请求。其中,ajax的最大优势在于其拦截器功能,通过拦截器可以对请求和响应进行拦截,并根据业务需求进行进一步处理。而在小程序开发中,我们可以通过对wx.request进行二次封装,从而实现类似于axios的功能。
源码地址git
npm地址npm
封装后支持以下功能:
- 拦截器
- 取消请求
- 使用方式axios(url[, config]),axios.request(config),axios.get(url[, config])...
定义构造函数
默认配置
- 默认使用get请求
- 在发起请求时,会自动将baseUrl和url进行拼接。
// defaults.js
export default {
baseUrl: '',
method: 'get'
}
Axios
合并默认配置和实例配置,并将合并后的结果赋值给this.defaults,使得在Axios实例中可以方便地访问配置项。
// Axios.js
import defaults from '../defaults/index'
function Axios(config) {
// 合并默认配置和实例配置
this.defaults = { ...defaults, ...config }
}
添加请求
添加请求方法到Axios原型
-
定义Axios.prototype.request方法,该方法暂未实现功能,目前只是一个占位符。
-
遍历所有请求方式添加到Axios的原型上,其内部通过调用this.request方法发送请求。
// Axios.js
// 公共请求
Axios.prototype.request = function(){
}
// 其他请求方式
const methods = ['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT']
// 遍历请求方式添加到原型
methods.forEach(method => {
Axios.prototype[method.toLowerCase()] = function(config) {
return this.request({ ...config, method: method.toLowerCase() })
}
})
封装公共请求
- 支持axios(config) 或 axios(url, config)调用方式
- axios(config),参数为配置信息,配置信息和wx.request一致
- axios(url, config),参数一为请求url, 参数二为配置信息
请求适配器
- 在发起请求时,如果配置了 baseUrl,将会自动拼接 baseUrl 和 url,然后发送请求。如果未配置 baseUrl,则直接使用 url 发起请求。
- rest 是一些其他的配置信息,和 wx.request 的配置信息是一致的。
- 如果状态码在 200 到 299 的范围内,则意味着请求成功。此时会返回响应结果和配置信息,并且 Promise 的状态将会被置为已满足(fulfilled)状态。
- 如果状态码不在 200 到 299 的范围内,则意味着请求失败。此时会返回错误信息和配置信息,并且 Promise 的状态将会被置为已满足(rejected)状态。
- 该函数最终会返回一个 Promise 对象,以便调用者可以使用 then 和 catch 方法来处理请求的结果。
// utils.js
export function adapter(config) {
const { baseUrl, url, ...rest } = config
return new Promise((resolve, reject) => {
wx.request({
...rest,
// 拼接请求url
url: baseUrl ? baseUrl + url : url,
success(response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
// 请求成功, 状态码2xx
resolve({ ...config, ...response})
} else {
// 请求成功,非2xx的htt状态码
reject({ ...config, ...response})
}
},
fail(error) {
// 请求发送失败,断网或超时
reject({ ...config, ...error})
}
})
})
}
使用配置的适配器将请求分派到服务器
// utils.js
export function dispatchRequest(config) {
return adapter(config).then((response) => {
return response
}).catch((error) => {
return Promise.reject(error)
})
}
实现公共请求函数
- 该方法接收两个参数。当第一个参数是字符串时,被视为请求的 URL,而第二个参数则被视为请求的配置信息。如果第一个参数不是字符串,则直接将其作为配置信息。
- 将 Axios 的默认配置信息和本次请求的配置信息合并起来,作为本次请求的配置信息。
- 通过循环遍历 chains 数组,创建一个处理链。
// Axios.js
import { dispatchRequest } from '../utils'
// 公共请求方法
Axios.prototype.request = function(configOrUrl, config){
if (typeof configOrUrl === 'string') {
config = config || {}
config.url = configOrUrl
} else {
config = configOrUrl || {}
}
// 合并配置信息
config = { ...this.defaults, ...config }
const chains = [ dispatchRequest, undefined ]
let promise = Promise.resolve(config)
// 循环
while(chains.length) {
promise = promise.then(chains.shift(), chains.shift())
}
return promise
}
创建实例函数
复制Axios属性和方法,挂载到实例对象
// Axios.js
export function createInstance(config) {
// 创建实例
const context = new Axios(config)
// 通过bind改变request方法的this指向,以支持调用instance(config)
const instance = Axios.prototype.request.bind(context)
// 遍历Axios原型上的所有方法,挂载到实例
Object.keys(Axios.prototype).forEach((key) => {
instance[key] = Axios.prototype[key]
})
// 遍历Axios所有属性,挂载到实例
Object.keys(context).forEach((key) => {
instance[key] = context[key]
})
return instance
}
使用
// 创建实例
const axios = createInstance({ baseUrl: 'http://localhost:3000' })
// 方式1
axios({
url: '/posts', data: { id: 2 }, method: 'get'
})
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
// 方式2
axios.request('/posts', { data: { id: 1 }})
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
// 方式3
axios.post({
url: '/posts', data: { id: 2 }
})
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
// ...
添加拦截器功能
InterceptorManager
在定义 InterceptorManager 类时,我们使用 use 方法来接受两个参数,fulfilled 作为 Promise 成功回调,reject 作为 Promise 失败回调(可选)。然后,我们将 fulfilled 和 reject 作为对象 { fulfilled, reject } 的属性添加到 handles 数组中。
// InterceptorManager.js
class InterceptorManager {
constructor() {
this.handles = []
}
use(fulfilled, reject) {
this.handles.push({ fulfilled, reject })
}
}
export default InterceptorManager
Axios添加拦截器
Axios构造函数中添加了拦截器属性interceptors,其中包括了请求拦截器interceptors.request和响应拦截器interceptors.response。
import { dispatchRequest } from '../utils'
import defaults from '../defaults/index'
// +
import InterceptorManager from './InterceptorManager'
function Axios(config) {
this.defaults = { ...defaults, ...config }
// + 拦截器
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
}
}
公共请求处理拦截器
通过遍历请求拦截器数组response.handles,使用unshift方法将handle.fulfilled和handle.reject依次添加到chains数组的头部。 通过遍历响应拦截器数组response.handles,使用push方法将handle.fulfilled和handle.reject依次添加到chains数组的末尾。 在循环 chains 数组时,通过请求拦截器逐个处理请求配置信息,然后将配置信息传递给 dispatchRequest 来发送请求,并将响应结果交由响应拦截器处理。
// Axios.js
Axios.prototype.request = function(configOrUrl, config){
if (typeof configOrUrl === 'string') {
config = config || {}
config.url = configOrUrl
} else {
config = configOrUrl || {}
}
// 合并配置信息
config = { ...this.defaults, ...config }
const chains = [ dispatchRequest, undefined ]
let promise = Promise.resolve(config)
// + 遍历请求拦截器,使用unshift添加到chains数组前面
this.interceptors.request.handles.forEach(handle => {
chains.unshift(handle.fulfilled, handle.reject)
})
// + 遍历响应拦截器,使用push追加到chains数组
this.interceptors.response.handles.forEach(handle => {
chains.push(handle.fulfilled, handle.reject)
})
while(chains.length) {
promise = promise.then(chains.shift(), chains.shift())
}
return promise
}
实例使用拦截器
// 创建实例
const axios = createInstance({ baseUrl: 'http://localhost:3000' })
// 添加请求拦截器
axios.interceptors.request.use((config) => {
config.header = {
token: 'xxx'
}
return config
})
// 添加响应拦截器
axios.interceptors.response.use((response) => {
return response.data
}, (error) => {
return Promise.reject(error)
})
取消请求
CancelToken
// utils.js
export function CancelToken(execute) {
let cancel;
this.promise = new Promise(resolve => {
cancel = resolve
})
execute(() => {
cancel()
})
}
为请求适配器添加取消方法
// utils.js
export function adapter(config) {
// + cancelToken
const { baseUrl, cancelToken, url, ...rest } = config
return new Promise((resolve, reject) => {
// + requestTask
const requestTask = wx.request({
...rest,
url: baseUrl ? baseUrl + url : url,
success(response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
// 请求成功, 状态码2xx
resolve({ ...config, ...response})
} else {
// 请求成功,非2xx的htt状态码
reject({ ...config, ...response})
}
},
fail(error) {
// 请求发送失败,断网或请求超时了
reject({ ...config, ...error})
}
})
// +
if (cancelToken) {
cancelToken.promise.then(function() {
requestTask.abort()
})
}
})
}
使用取消请求
const axios = createInstance({ baseUrl: 'http://localhost:3000' })
let cancel;
const cancelToken = new CancelToken(function(_cancel) {
cancel = _cancel
})
axios({
url: '/posts', data: { id: 2 }, method: 'get', cancelToken
})
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
// 取消请求
cancel()
转载自:https://juejin.cn/post/7252546940105490490