我们先简单描述一下这个几个名词区别,避免我们在后续讨论的过程中产生歧义,给预请求下一个简单的定义
在开始之前通过下面的图片可以看一下我们具体优化场景,便于后续更好的讨论解决方案
上图是机票列表页接口的耗时,整整花费了6S,可以看出来,这其中的优化空间还是很大的,而且这个页面可以交互的前提是列表页渲染出来
预请求和预加载很类似,合适的时机提前发起请求,然后存在某个地方,下一次获取时直接从缓存中获取,这是一个简单的模型,我们用一张图来表示如下:
通过上述模型我们不难发现,有一个关键点我们必须解决
如何统一劫持请求,如果不能劫持请求,我们就没办法控制请求是否走缓存
预请求的方案并不是我们原创,事实上已经有很多技术栈开始采用这种方案来解决问题,useQuery,SWR等提供了缓存请求结果的方案,但是这些都有一个前提是页面都采用了相同的技术栈,对于跨应用跨技术栈的场景我们需要一个更加通用的方案,毕竟将现有的请求库全部替换的RIO并不高。
以下是目前内部采用的一些方案
1、利用BFF进行缓存
在首页时,寻找合适的时机提前发送请求给BFF,BFF在拿到结果后将内容缓存到Redis中,在下一次正式请求发起时,如果能够拿到Redis缓存可以直接返回从而提升接口响应的速度
2、利用Native进行缓存
这种方案比较适用于混合开发,Native+H5的方式,客户端负责劫持请求并缓存内容,当下一次重复的请求发起时可直接返回缓存
3、利用serviceWorker进行缓存
基于我们对通用方案的要求,最终我们选择了ServerWorker作为缓存和劫持请求的基础
通过ServiceWorker劫持缓存
如果想要完成预请求,首先我们要对fetch事件有所掌控,我们可以修改全局的fetch方法或者统一收口请求方法,这两种方法都免不了对现有项目的侵入,统一请求库目前还不具备基础,servcieWorker正好提供了这样的能力,也非常适合做请求的拦截,我们只需要对serviceWorker稍加定制即可实现一个简单且通用的预请求方案。
通过在请求头中添加特殊标识
在劫持请求后我们需要识别当前请求是否预请求,因为预请求和正常请求的逻辑有所区别,预请求在请求完成后需要将请求出入缓存,而正常请求需要在发起真实请求之前,查找当前是否有可用的缓存,如果有缓存,直接返回缓存,这样就加快了第二次请求的速度,也避免了多余的网路开销
在发起预请求之前,在requestheader添加特殊标识,这样serviceWorker就能知道当前的请求是否预请求
利用request的信息typeurlbody计算出cacheKey,通过cacheKey存入和读取缓存
在解决了上述两个问题后,还有一个关键问题需要解决,什么样的请求认为是同一个请求,为了确保只有相同的请求,需要尽量保留了请求的信息,如:requestbody,requesturl,requestmethod,所以我们的基本思路是通过以上请求信息计算cachekey,但是如果简单粗暴的这么实现任然有些问题,因为在请求中有一些特殊参数,例如GE,X-tradeid,这类参数是为了为了更好的追踪这次请求而增加的参数,如果我们将这些参数也包含在计算cachekey的信息中,那么我们始终无法命中缓存,因为GE每一次请求都会发生变化,所以我们在计算cache之前需要排除这些干扰信息
通过对请求参数+URL+Type计算出cachekey,如果是预请求,通过cachekey存入缓存;如果是正常请求,利用cachekey查找缓存这样就能保证相同的请求命中缓存。
以上就是基于serviceWorker的方案设计,如果感兴趣的可以留言,在后续更新实践效果以及开源的版本