如今前端发展浩浩荡荡,多端开发必不可少。然而无论多少个端加入开发,其必定离不开网络请求这一层,除非那些呈现的内容都是随着coding直接输出的。大多数情况需要通过发起网络请求而获取。所以多端开发肯定要面对一个问题,那就是发请求这件事或者这个功能如何在多端适配?
可能见过如下这种类似的封装代码
// case 1
import axios from 'axios'
function request(...) {
// create
return axios(...)
}
// case 2
import axios from 'axios'
function request(...){
return new Promise(function(resolve, reject){
return axios({...}).then(function(res){
resolve(res)
}).catch(function(error){
reject(error)
})
})
}
// case 3
import axios from 'axios'
export function request() {....}
export function fetch() { ... }
export function get() { ... }
export function post() { ... }
// a.js
function someApi1() {
return request(....)
}
// b.js
function someApi2() {
return request(....)
}
首先这三种请求功能的封装都能起到请求收口的作用,但感觉有点鸡肋。理由是一般依赖的请求库(比如这里axios)里都提供默认的以上三种封装,无论是request还是单个请求方式不同的方法名(如get/post/delete/等),直接用难道不香吗?但总能见到一些小伙伴们乐此不疲的在其项目根目录下又造同一个轮子,可能考虑在请求入口处做一些自定义的处理。但确实是依赖库做得很完善,连这块都给你想到了,提供了请求和响应的两种interceptors(拦截器),支持传入请求响应前后预处理操作,比如请求loading、参数处理、请求头token类字段追加等等行为。更有甚者这个玩意还能用到nodejs服务端发起一个请求,能在web和server间无缝丝滑使用,真的是神器。问一句你真的会用axios吗?
话说回来,看过axios源码的一定知晓这个玩意的核心封装就在于adapter(适配器)。懂的人都懂,要无缝丝滑玩转于web和server两端,无非就是要判断调用请求的宿主环境。然后借助宿主环境提供的天生神力(api)来加持调用丝滑感嘛。比如在web端则是XMLHttpRequest,在server端则有http和https这种native模块加持,剩下的就是组装api调用细节咯。当然可内服,也支持外传,axios支持调用方传入adapter,哪里传呢?直接上码吧~
export function request(configOrUrl, config) {
// ....
// 此处省略一万个为什么细节铺垫
// ....
let newConfig = config;
// ...
try {
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error);
}
// ...
}
export default function dispatchRequest(config) {
throwIfCancellationRequested(config);
config.headers = AxiosHeaders.from(config.headers);
// Transform request data
config.data = transformData.call(
config,
config.transformRequest
);
if (['post', 'put', 'patch'].indexOf(config.method) !== -1) {
config.headers.setContentType('application/x-www-form-urlencoded', false);
}
// 看这里,看这里,看这里 config.adapter是关键,可外部传入
const adapter = adapters.getAdapter(config.adapter || defaults.adapter);
return adapter(config)
}
以上截取核心源码说明下。个人感觉优秀的封装必须具备两种情怀,第一种允许内部的自嗨,第二种接纳外面的精彩。官方说法就是:在满足既定功能完整调用的同时,暴露外部接口支持插拔。
那么,本文要借助这个神器的封装思想,去干另外一件事,那就是那么长的文章标题所要表达的意思。日常面临的更多是跨多端业务层的请求调用,是不是每个端都要写那么一个request函数呢?很显然分析了上述代码后,自然不用,如果能基于axios封装,在不同宿主环境下提供对应的适配器完成http的活,而request封装的前后细节交给一个抽象层,岂不更香?上码
// 请求api包装器,不重复造轮子
// 请求具体细节丢给宿主环境的api调用处理,此处只用于聚合
function makeRequest(apiConfig, request = defaultAdapter, requestInterceptor, responseInterceptor){
return Object.keys(apiConfig).reduce((obj, apiName) => {
const apiConfigValue = apiConfig[apiName]
const typeApiConfigValue = typeof apiConfigValue
if (!apiConfigValue) return obj
if (typeApiConfigValue === 'function') {
obj[apiName] = apiConfigValue
} else if (typeApiConfigValue === 'object') {
if (typeof apiConfigValue.url !== 'string') {
obj[apiName] = makeRequest(apiConfig[apiName], request, requestInterceptor, responseInterceptor)
} else {
obj[apiName] = async (params, ...rest) => {
// 请求前的处理
requestInterceptor(...)
const response = await request(...)
return responseInterceptor(response)
} catch (error) {
return responseInterceptor(error)
}
}
}
}
return obj
}, {})
}
// 默认适配器
function defaultAdapter() { .... }
// 小程序以微信为例, 百度/抖音/京东等改个名皆可用
function wxRequestAdapter(){
return new Promise((resolve, reject) => {
wx.request({
url: fullUrl,
...
success: resolve,
fail: reject
})
})
}
// a业务下api汇总,支持json文件序列化,可配置,便于数据传输
// 接口调用清晰明白,支持追加说明字段
// 代码即文档,配置即文档,无须额外注解
const apiJSON = {
a: {
url: '/a',
method: 'post',
data: {}
},
b: {
url: '/a',
method: 'get',
data: {}
},
// 支持嵌套
c: {
d: {
url: '/x/y'
}
}
}
// 注册
const {a, b, c} = makerRequest(apiJSON)
a(...).then(...)
b(...).then(...)
c.d(...).then(...)
上述代码只简单提供一个参考,具体实现可参考完善丰富。欢迎小伙伴们的唾弃~~~