小程序优化-请求缓存与预请求优化

一、预请求概念

首先在一开始还是先明确下这里所提及到的“预请求”的概念和常规的 http 的 options 请求有所区别,这篇文章所涉及到的预请求的概念都是在页面切换时候的页面请求的提请发送,跳转进入新页面后能够快速的获取到服务端的数据。

1.1 预请求的业务含义

为啥需要要做这个预请求的处理呢?很明显都是为了能够提升应用的运行性能,进而提升用户的使用体验。


1.2 预请求的构思

实现目标

在进入页面跳转前预先发送请求,跳转页面后获取对应的请求结果,实现请求和切换页面一起异步进行。

技术根基与原理:

Request 请求结果的缓存与获取。

实现思路:

比较简单的实现就是对 fetch 请求方法进行一个请求缓存的封装操作,然后在页面跳转前进行一个设置缓存的请求,跳转页面后再进行从请求缓存集当中获取请求结果。





二、具体封装与改造

2.1 封装请求处理

封装请求 fetch hooks

  • 请求结果缓存的实现
    • 简单暴力的实现就是将请求结果缓存到 Map 结构对象当中
  • 判断读取缓存的请求结果
    • 再次请求时候判断是否需要缓存并且在缓存集 Map 当中是否已经有缓存结果
 
typescript
复制代码
// /plugins/http.ts

export const $request = ({ url = '', method = 'GET', data }: { url: string, method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT', data?: any }): Promise<any> => {
  return new Promise((resolve, reject) => {
    uni.request({
      url, data, method,
      success: res => resolve(res),
      fail: err => reject(err),
    })
  })
}
 
typescript
复制代码
// hooks/useFetch.ts

import { ref, onBeforeUnmount, } from 'vue'

import { $request as httpRequest } from '@/plugins/http'

import usePreFetch from '@/hooks/usePreFetch'

const FETCH_RESPONSE_CACHE_MAP = new Map()      // 请求成功结果缓存集

/**
 * 生成 Key
 */
const _generateKey = (data: any) => {
  return JSON.stringify(JSON.parse(JSON.stringify(data || '')))
}

export default function (data: any) {
  let fetchRes = null
  let useCache = !!(data.useCache || false) // 需要请求缓存 OR 需要预请求
  const _fetchKey = _generateKey(data)
  const fetchLoading = ref(false)

  const httpFetch = async () => {
    fetchLoading.value = true

    // 判断是否需要使用缓存并且是否已经有缓存了则从请求缓存集当中获取对应缓存结果并返回
    if (useCache && FETCH_RESPONSE_CACHE_MAP.get(_fetchKey)) {
      fetchRes = FETCH_RESPONSE_CACHE_MAP.get(_fetchKey)

      FETCH_RESPONSE_CACHE_MAP.delete(_fetchKey)
      
      return fetchRes
    }

    // 执行请求逻辑,获取请求结果
		fetchRes = await httpRequest(data)

    // 需要缓存请求时将请求结果 Promise 封装并且设置在请求缓存集中
    if (useCache) {
      FETCH_RESPONSE_CACHE_MAP.set(_fetchKey, Promise.resolve(fetchRes))
    }

    fetchLoading.value = false

    return fetchRes
  }
  
  return {
    fetchLoading,
    http: httpFetch,
  }
}

2.2 改造使用

跳转前预请求

  • 在调用 navigationTo 进行跳转前设置一个 useCache 参数告诉 useFetch 需要进行请求的缓存处理

跳转后获取请求结果

  • 跳转后再次请求,封装的 useFetch hooks 方法内部去获取缓存请求集当中获取对应请求的缓存
 
dart
复制代码
// 封装的请求方法
const getXyzInfo = (id = '', useCache = false) => {
  const {
    http,
  } = useFetch({
    useCache,
    url: 'xyz/info',
    data: {
      id,
    },
  })

  return http()
}


// 跳转时候逻辑
getXyzInfo('xxxx', true)
uni.navigationTo({
  url: 'pages/xyz/index',
})


// 跳转后页面的生命周期
onMounted(async () => {
  const res = await getXyzInfo('xxxx', true)
  // ...
})





三、优化结果

3.1 性能优化的指标

传统的白屏时间、首屏时间

使用Performance.timingapi 提供的时间节点进行计算页面的一些时间指标。

白屏时间:浏览器开始显示内容的时间,所以我们一般认为解析完的时刻,或者开始渲染标签就是该页面白屏结束的时间。

  • performance.timing.domLoading - performance.timing.navigationStart

首屏时间:首屏时间是指用户打开一个网站时,直到浏览器首页面内容渲染完成的时间。

  • performance.timing.domInteractive - performance.timing.navigationStart

虽然白屏时间和首屏时间能够一定程度展现这个预请求技术改造的性能优化指标,但是感觉还未能算精确的。

更为精准的统计时间

更精准的时间应该是从上一个页面进行调用跳转的 api 到进入新页面开始接受到请求响应回来的时间,因此这里是使用Performance的实例 api 进行一个时间指标的收集操作:

  • 在封装的路由跳转方法navigateTo当中利用performance.mark手动打点这次跳转开始的时间;
  • 在业务页面当中请求fetch成功返回的回调当中再次通过performance.mark打点当前时间节点的时间戳;
  • 通过performance.measure计算得出适合当前场景的较为精准的统计时间。
 
javascript
复制代码
// 跳转时候逻辑
performance.mark('preFetchAndNavigationTiming-start')
getXyzInfo('xxxx', true)
uni.navigationTo({
  url: 'pages/xyz/index',
})


// 跳转后页面的生命周期
onMounted(async () => {
  const res = await getXyzInfo('xxxx', true)
  performance.mark('preFetchAndNavigationTiming-end')
  
  // ...

  computedPreFetchAndNavigationTiming()
})

// 封装的获取统计时间方法
function computedPreFetchAndNavigationTiming() {
  performance.measure('preFetchAndNavigationTiming', 'preFetchAndNavigationTiming-start', 'preFetchAndNavigationTiming-end')
  const measures = performance.getEntriesByName('preFetchAndNavigationTiming')
  const measure = measures[0]

	console.info('preFetchAndNavigationTiming milliseconds: ', measure.duration)
  
  // 清除存储的标志位
  performance.clearMarks();
  performance.clearMeasures();
}

3.2 优化的成效

通过上一小节的统计指标的测量,在一个正常网络的正常手机上在页面跳转接入预请求改造后可提升节约了大概 200 - 300ms 的时间,算是证明这个预请求的改造是有一定程度的性能提升,虽然带给用户的体验的提升可能并不算很明显。


3.3 思考其中的不足

更有效的缓存优化策略

目前项目当中只是仅仅粗暴的做了一个 Map 的数据结构对象简单暴力的对请求缓存进行一个存储,但是更有效的缓存策略可以参考 LRU 算法对请求结果缓存的进行一个优化储存策略改造。这里就不再展开,参考资料内有相关的文章,可根据实际自寻。

更科学与智能化的预请求配置

目前还是需要在业务跳转前进行一个预请求的 fetch 调用,跳转后再次调用 fetch 获取缓存;在跳转路由时候存在一个配置化,在封装的 fetch hooks 里面自动调用 preFetch 预请求,避免业务方在多次跳转重复多次写预请求的逻辑代码。

 
posted @ 2023-05-15 09:22  这个夏天要冰凉  阅读(117)  评论(0编辑  收藏  举报