需求
提交小程序审核时,有一个体验测评,产品让我们根据小程序的体验测评报告去优化小程序。
其中有一项是网络请求的优化,给我们出了很大的难题。
文档中是这样解释的:3分钟以内同一个url
请求不出现两次回包大于128KB
且一模一样的内容
看到这个问题的时候,首先想到的是在响应头上加上cache-control
,经过测试发现小程序并不支持网路请求缓存。搜索发现官方明确答复,小程序不支持网络请求缓存:wx.request不支持http缓存
既然官方不支持网络请求缓存,那只能自己想办法解决这个问题了。
先来看一下需求:3
分钟内,同一请求只能请求一次。
分析
分析:
- 只需做
GET
请求的网络缓存。 - 缓存时间如何控制。
- 做了缓存之后,如何知道
3
分钟内,这个请求在服务端数据有没更新。 - 提交
GET
请求前,先检查本地有没有缓存 - 分页怎么缓存怎么处理
前两点比较好实现,虽然小程序不支持网络请求缓存,但我们还是可以利用cache-control
来实现这个功能。
首先网络请求需不需要情缓存统一交给服务端去做,服务端在处理GET
请求时,统一加上响应头cache-control
,如果需要缓存就用max-age=180
,如果不需要做网络请求就用no-cache
。前端根据响应头信息自己做前端缓存。
其中的难点是前端如何知道服务端数据有没更新,如果服务端数据更新了,前端还是使用缓存这是有问题的。
经过一番思考后发现,前端提交数据后,相应的GET
请求数据会更新,也就是说前端只要有数据提交,就应该把缓存清空。
这有一个难点,当前端提交数据时,前端是不知道哪些GET
请求会因此更新数据,所以这个问题我们没有解决,我的方法比较粗暴:只要前端提交了数据,就将所有缓存清空。这是一个治标不治本的问题。
最后分页请求如何处理,我们的分页是放在请求头range
,后端会有一个响应头content-range
,所以这里存在缓存的key
就要加上rang
,格式是rang--api
实现
公司项目封装了HTTP
请求
-
拦截请求,如果是
GET
请求,检查缓存,- 如果缓存没过期,将缓存返回出去,不再发请求
- 如果缓存过期,发请求
if (request.method.toLowerCase() === "get"){
// param 请求信息
const cache = this.handleCatchControl(request)
if (!cache.isRequest)
return this.listener.onApiResponse(request, 200, cache.data), sequence; //将缓存返回给对应的请求
}
- 缓存网络请求
// param 响应头,上下文,响应数据
this.setCatchControl(headers, context, response.data)
-
两个工具函数
- 处理网络缓存
- 设置网络缓存
-
设置网络请求
-
GET
请求缓存数据,其他请求清空数据 - 数据格式:
//如果同时发起多个`GET`请求,需要拼接之前缓存数据 ApiAgent.cacheData = Object.assign(ApiAgent.cacheData,{ [context.request.url]: { //api data, //响应数据 expireTime: Number(cacheControl.split("=")[1] + '000'), //过期时间 cacheTime: new Date().getTime(), //缓存时间 } })
-
// param 响应头,上下文,响应数据
setCatchControl(responseHeader: any, context: any, data: any) {
if (context.request.method.toLowerCase() === "get") {
const headers = HandleHeaders.get(responseHeader)
const cacheControl = headers["cache-control"]
if (cacheControl && cacheControl !== "no-cache") {
const key = this.handleRange(responseHeader, context.request.url)
ApiAgent.cacheData = Object.assign(ApiAgent.cacheData,{
[key]: {
data,
expireTime: Number(cacheControl.split("=")[1] + '000'),
cacheTime: new Date().getTime(),
}
})
}
} else {
ApiAgent.cacheData = {}
}
}
-
处理网络缓存
- 判断缓存是否存在
- 判断缓存有没过期,在设置缓存时,比对当前时间和缓存时间,是否小于失效时间
// param 请求信息
handleCatchControl(request): any {
const cacheArr = ApiAgent.cacheData
// 没有缓存
if (Object.keys(cacheArr).length === 0)
return { isRequest: true }
// 分页 key 处理
const key = this.handleRange(request.headers, request.url)
// 缓存处理
let cache = {}
Object.keys(cacheArr).forEach(cacheArrKey => {
if (cacheArrKey === request.url) {
cache = cacheArr[cacheArrKey]
}
})
const newDate = new Date().getTime()
// 有缓存
if (newDate - cache.cacheTime < expireTime){
return { isRequest: false, data: cache.data }
}
// 缓存过期
return { isRequest: true}
}
-
处理
range
,请求和响应的range
一起处理- 请求的
range
格式:range: objects=1-10
- 响应的
range
格式:content-range: objects 1-10/58
- 请求的
handleRange(headers: any, url: string): string {
if (!headers)
return url
const header = HandleHeaders.get(headers)
const reg = /(=|\s)([0-9]*[\-][0-9]*)\/?/
let range: string = ""
if (header.range) {
range = header.range.match(reg)[2]
} else if (header["content-range"]) {
range = header["content-range"].match(reg)[2]
}
if(range)
return `${range}--${url}`
return url
}
- 响应头全部变成小写,在小程序中,无法确定响应头的大小写会导致报错,所以统一处理响应头
class HandleHeaders {
static get(headers: { [key: string]: string }) {
const headersData: any = {}
Object.keys(headers).forEach(key => {
headersData[key.toLowerCase()] = headers[key]
})
return headersData
}
}
总结
有一点没有说,就是这个缓存是保存在哪里的?
既没有用localStorage
,也没有用globalapp
,用的是类的静态属性。
这样做有3
个好处:
- 使用
localStorage
数据不好清除,后期可维护性也较差 - 缓存挂在
globalapp
和请求无直接联系 - 无需在退出小程序时手动清理缓存
我在使用时遇到一个坑,是因为自己没有理解:类能保存数据的,不能保存状态,但类的对象是既可以保存数据,也可以保存状态的。
最后,此方法还是有很大的优化空间。
另外可添加微信ttxbg180218
交流:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。