前端api请求缓存方案
在Web应用程序的开发中,性能是一个不可或缺的话题。对于web打包的单页应用来说,优化性能的方法有很多,比如tree-shaking、模块懒加载、使用extrens network cdn来加速这些常规优化。即使在 vue-cli 项目中,我们也可以使用 --modern 指令生成新旧浏览器代码来优化程序。 事实上,缓存一定是改进Web应用程序的有效方法之一,尤其是在用户受限于网络速度的情况下。提
在Web应用程序的开发中,性能是一个不可或缺的话题。对于web打包的单页应用来说,优化性能的方法有很多,比如tree-shaking、模块懒加载、使用extrens network cdn来加速这些常规优化。即使在 vue-cli 项目中,我们也可以使用 --modern 指令生成新旧浏览器代码来优化程序。
事实上,缓存一定是改进Web应用程序的有效方法之一,尤其是在用户受限于网络速度的情况下。提高系统的响应能力,降低网络消耗。当然,内容离用户越近,缓存的速度就越快,缓存的效果也越好。
在客户端,我们有很多方式来缓存数据和资源,例如标准的浏览器缓存和当前的热门 Service Worker。但是,它们更适合缓存静态内容。比如html、js、css和图片。而缓存系统数据,我使用了另一种解决方案。
下面介绍一下我在项目中应用的各种api请求方案,从简单到复杂。
方案一数据缓存
简单的数据缓存,先请求获取数据,再使用数据,不再请求后端api。
代码如下:
常量数据缓存 u003d 新地图()
异步 getWares() {
让键 u003d '商品'
// 从数据缓存中获取数据
让数据 u003d dataCache.get(key)
如果(!数据){
// 无数据请求服务器
const res u003d await request.get('/getWares')
// 其他操作
...
数据 u003d ...
// 设置数据缓存
giver.set(键,数据)
}
返回数据
}
第一行代码使用 es6 或更高版本的地图。如果您对地图不是很了解,可以参考它们。
ECMAScript 6 介绍 Set 和 Map也许探索 ES6map 和 set 的介绍在这里可以理解为键值对存储结构。
然后代码使用了async函数,使得异步操作更加方便。你可以参考一下。ECMAScript 6 初始异步函数学习或巩固知识。
代码本身很容易理解。它使用 Map 对象来缓存数据,然后调用从 Map 对象中获取数据。对于其简单的业务场景,您可以直接使用此代码。
调用方式:
getWares().then(...)
// 第二次调用取之前的数据
getWares().then(...)
方案2承诺缓存
第一个方案本身是不够的。因为如果同时考虑两次以上调用这个api,会因为请求没有返回而进行第二次请求api。当然,如果在系统中加入像 vuex 和 redux 这样的单一数据源框架,就不太可能遇到这个问题,但有时我们希望为每个复杂的组件单独调用 API,而不是组件之间进行数据通信。
常量 promiseCache u003d 新地图()
getWares() {
常量键 u003d '商品'
让 promise u003d promiseCache.get(key);
// 当前的 Promise 缓存中没有 Promise
如果(!承诺){
承诺 u003d request.get('/getWares').then(res u003d> {
// 对 res 进行操作
...
}).catch(错误 u003d> {
// 请求回来后,如果有问题,从缓存中删除promise,避免第二次请求继续出错 S
promiseCache.delete(键)
返回 Promise.reject(错误)
})
}
// 返回承诺
回报承诺
}
这段代码避免了方案一中同时多个请求的问题。同时,promise 在后端错误的情况下被删除,这样就不会有缓存错误的promise 总是出错的问题。
调用方式:
getWares().then(...)
// 第二次调用以获取先前的承诺
getWares().then(...)
zoz100027`
方案3 多承诺缓存
该方案是在需要多个 api 请求时,如果出现 api 错误,则同时返回数据。没有返回正确的数据。
常量查询 u003d{
商品:'getWares',
咬:'getSku'
}
常量 promiseCache u003d 新地图()
异步 queryAll(queryApiName) {
// 判断传入的数据是否为数组?
常量 queryIsArray u003d Array.isArray(queryApiName)
// 统一处理数据,不管是字符串还是数组都被当作数组处理
常量 apis u003d queryIsArray ? queryApiName : [queryApiName]
// 获取所有请求服务
常量 promiseApi u003d []
apis.forEach(api u003d> {
// 使用承诺
让 promise u003d promiseCache.get(api)
如果(承诺){
// 如果缓存中有,直接push
promise.push(承诺)
} 其他 {
承诺 u003d request.get(query[api]).then(res u003d> {
// 对 res 进行操作
...
}).catch(错误 u003d> {
// 请求回来后,如果有问题,从缓存中删除promise
promiseCache.delete(api)
返回 Promise.reject(错误)
})
promiseCache.set(api, 承诺)
promiseCache.push(承诺)
}
})
return Promise.all(promiseApi).then(res u003d> {
// 根据传入的是字符串还是数组返回数据,因为它本身就是数组操作。
// 如果传入的是字符串,需要取出来
返回查询IsArray ?水库:水库[0]
})
}
该方案是一种同时从多个服务器获取数据的方式。可以同时获取多个数据进行操作,不会因单个数据问题而出现错误。
通话方式
queryAll('wares').then( ... )
// 第二次调用不会获取商品,而是获取 skus。
queryAll(['wares', 'skus']).then( ... )
方案4 增加时间相关缓存
通常缓存是有害的。如果我们知道数据被修改了,我们可以直接删除缓存。这时候,我们就可以调用该方法向服务器请求了。这样,我们就避免了在前端显示旧数据。但是我们可能会在一段时间内不对数据进行操作,那么此时旧数据会一直存在,所以我们最好设置一个时间来移除数据。
该方案使用类持久数据作为数据缓存,并添加过期数据和参数化。
代码如下:
首先,定义一个可以存储promise或数据的持久化类
类项目缓存() {
构造(数据,超时){
this.data u003d 数据
// 设置超时时间,多少秒
this.timeout u003d 超时
// 创建对象的时间,近似设置为获取数据的时间
this.cacheTime u003d (new Date()).getTime
}
}
祖兹 100033-
然后我们定义数据缓存。我们使用与 Map 相同的 api
类过期缓存 {
// 将静态数据映射定义为缓存池
静态缓存映射 u003d 新映射()
// 是否数据超时
静态 isOverTime(名称) {
常量数据 u003d ExpriesCache.cacheMap.get(name)
// 没有数据必须超时
如果(!数据)返回真
// 获取系统当前时间戳
const currentTime u003d (new Date()).getTime()
// 获取当前时间过去的秒数和存储时间
常量超时 u003d (currentTime - data.cacheTime) / 1000
// 如果过去的秒数大于当前超时时间,则返回null,让它去服务器取数据。
if (Math.abs(overTime) > data.timeout) {
//这段代码可能有问题,也可能没有问题,但是如果有这段代码,重新进入方法可以减少判断。
ExpriesCache.cacheMap.delete(名称)
返回真
}
// 没有超时
返回假
}
// 是当前缓存中的数据超时
静态有(名称){
返回 !ExpriesCache.isOverTime(名称)
}
// 从缓存中删除数据
静态删除(名称){
返回 ExpriesCache.cacheMap.delete(名称)
}
// 获取
静态获取(名称){
常量 isDataOverTiem u003d ExpriesCache.isOverTime(名称)
//如果数据超时,返回null,但不返回ItemCache对象
返回 isDataOverTiem ?空:ExpriesCache.cacheMap.get(name).data
}
// 默认存储20分钟
静态集(名称、数据、超时 u003d 1200){
// 设置itemCache
常量 itemCache u003d mew ItemCache(数据,超时)
//缓存
ExpriesCache.cacheMap.set(name, itemCache)
}
}
至此,数据类和操作类已经定义好了,我们可以在api级别进行定义。
zoz100036`
// 生成键值错误
const generateKeyError u003d new Error("无法从名称和参数生成密钥")
// 生成键值
函数生成键(名称,参数){
// 从参数中获取数据并将其转换为数组
常量参数 u003d Array.from(argument).join(',')
试试{
// 返回一个字符串,函数名+函数参数
返回
${name}:${params}
}捕捉(_){
// 返回生成的密钥错误
返回生成键错误
}
}
异步 getWare(参数 1,参数 2){
// 生成密钥
const key u003d generateKey('getWare', [params1, params2])
// 获取数据
让数据 u003d ExpriesCache.get(key)
如果(!数据){
const res u003d await request('/getWares', {params1, params2})
// 使用10s缓存,10s后再次get获取null,继续向服务器请求
expSkache.set(key, res, 10)
}
返回数据
}
该方案使用不同的缓存方式,不同的过期时间和api参数。大多数业务场景已经可以满足。
通话方式
getWares(1,2).then(...)
// 第二次调用以获取先前的承诺
getWares(1,2).then(...)
// 不同的参数,没有之前的promise
getWares(1,3).then(...)
方案5:基于修饰符的方案4
与方案4的解法一致,但基于修饰符。
代码如下:
// 生成键值错误
const generateKeyError u003d new Error("无法从名称和参数生成密钥")
// 生成键值
函数生成键(名称,参数){
// 从参数中获取数据并将其转换为数组
常量参数 u003d Array.from(argument).join(',')
试试{
// 返回字符串
返回
${name}:${params}
}捕捉(_){
返回生成键错误
}
}
函数装饰(句柄描述,入口参数){
//判断当前最终数据是否为descriptor,如果是descriptor,直接使用?
// log等修饰符
if (isDescriptor(entryArgs[entryArgs.length - 1])) {
返回句柄描述(...entryArgs,[])
} 其他 {
// 如果没有
// 修饰符如 add(1) plus(20)
返回函数() {
return handleDescription(...Array.protptype.slice.call(arguments), entryArgs)
}
}
}
函数句柄ApiCache(目标,名称,描述符,...配置){
// 获取函数体并保存
const fn u003d 描述符.值
// 修改函数体
描述符. 值 u003d 函数 () {
const key u003d generateKey(名称,参数)
// 无法生成key,直接请求服务端数据
if (key u003du003du003d generateKeyError) {
// 使用刚刚保存的函数体发出请求
return fn.apply(null, arguments)
}
让承诺 u003d ExpriesCache.get(key)
如果(!承诺){
// 设置承诺
promise u003d fn.apply(null, arguments).catch(error u003d> {
// 请求回来后,如果有问题,从缓存中删除promise
过期缓存.delete(key)
// 返回错误
返回 Promise.reject(错误)
})
// 使用10s缓存,10s后再次get获取null,继续向服务器请求
ExpriesCache.set(key, promise, config[0])
}
回报承诺
}
返回描述符;
}
// 开发修饰符
函数 ApiCache(...args) {
返回装饰(handleApiCache,参数)
}
此时,我们使用类来缓存api
类 API {
// 缓存 10s
@ApiCache (10)
// 此时不要使用默认值,因为当前修饰符无法获取。
getWare(参数 1,参数 2){
return request.get('/getWares')
}
}
无法将函数用作修饰符,因为函数具有函数提升。
例如:
变量计数器 u003d 0;
变量添加 u003d 函数 () {
计数器++;
};
@添加
函数 foo() {
}
代码本打算执行后counter等于1,但实际结果是counter等于0。由于函数提升,实际执行的代码如下
@添加
函数 foo() {
}
是计数器;
变量添加;
计数器 u003d 0;
添加 u003d 函数 () {
计数器++;
};
所以没有办法在函数上使用修饰符。具体参考ECMAScript 6 介绍装饰器
这种方式写起来简单,对业务层影响不大。但是缓存时间不能动态修改
调用方法
getWares(1,2).then(...)
// 第二次调用以获取先前的承诺
getWares(1,2).then(...)
// 不同的参数,没有之前的promise
getWares(1,3).then(...)
总结
api的缓存机制和场景也基本介绍到这里了。基本可以完成绝大多数数据业务的缓存。这里我也想问问大家有没有更好的解决办法,或者这个博客有什么问题。欢迎指正,在此谢谢。
同时这里还有很多未完成的工作,以后可能会在博客中不断完善。
更多推荐
所有评论(0)