在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的缓存机制和场景也基本介绍到这里了。基本可以完成绝大多数数据业务的缓存。这里我也想问问大家有没有更好的解决办法,或者这个博客有什么问题。欢迎指正,在此谢谢。

同时这里还有很多未完成的工作,以后可能会在博客中不断完善。

Logo

前往低代码交流专区

更多推荐