Front-end persistence cache optimization

Caching is one of the effective ways to improve web applications, especially when users are limited by network speed. Improve system responsiveness and reduce network consumption. Of course, the closer the content is to the user, the faster the cache will be and the more effective the cache will be.

I have personally written a front-end API request caching solution before . The in-memory cache and expiration logic are introduced. Later, I also wrote a front-end storage tool library , which used adapters to handle different storage media (memory, IndexedDB, localStorage, etc.).

However, caching still needs to be optimized in some specific scenarios. For example, users need to obtain necessary data through certain interfaces when logging in or filling out forms, and these interfaces are provided by third-party platforms. These interfaces may experience errors or timeouts. If the current data is very real-time, the developer must retry or contact the third-party platform to handle the corresponding error. If the real-time nature of the data is not strong, local caching can currently be used.

Generally speaking, when obtaining time-sensitive cache, we will check and delete the current data. The code abbreviation is as follows:

// The corresponding modules and functions of the cache 
const  EXTRA_INFO_CACHE_KEY = 'xxx.xxx.xxx' ;
 // The cache duration is 7 days 
const  CACHE_TIME =   7 * 24 * 60 * 60 * 1000 ;

const  getCachedExtraInfo = () => {
   const cacheStr = localStorage . getItem ( ` ${EXTRA_INFO_CACHE_KEY} . ${userId} ` );

  if (!cacheStr) {
     return  null ;
  }

  let cache = null ;
   try {
    cache = JSON . parse (cacheStr);
  } catch () {
     return  null ;
  }

  if (!cache) {
     return  null ;
  }

  // The cache has expired, return null directly 
  if ((cache. expiredTime ?? 0 ) < new  Date (). getTime ()) {
     return  null ;
  }

  return cache.data ;​
}

const  getExtraInfo = () => {
   const cacheData = getCachedExtraInfo ();
   if (cacheData) {
     return  Promise . resolve (cacheData);
  }

  return  getExtraInfoApi (). then ( res => {
     localStorage . setItem ( ` ${EXTRA_INFO_CACHE_KEY} . ${userId} ` , {
       data : res,
       expiredTime : ( new  Data ()). getTime () + CACHE_TIME ,
    });
    return res;
  });
}

If there is an access error problem in the interface at this time, many users whose data has expired will not be able to use the function normally. Adding a retry function at this time may solve some errors. At this time, we will not consider the logic of retrying.

Considering that the vast majority of users’ corresponding data will not be modified, the corresponding code does not need to delete the data. Instead, a timeout flag is returned.

const  EXTRA_INFO_CACHE_KEY = 'xxx.xxx.xxx' ;
 const  CACHE_TIME =   7 * 24 * 60 * 60 * 1000 ;

const  getCachedExtraInfo = () => {
   const cacheStr = localStorage . getItem ( ` ${EXTRA_INFO_CACHE_KEY} . ${userId} ` );

  if (!cacheStr) {
     return  null ;
  }

  let cache = null ;
   try {
    cache = JSON . parse (cacheStr)
  } catch () {
     return  null ;
  }

  if (!cache) {
     return  null ;
  }

  if ((cache. expiredTime ?? 0 ) < new  Date (). getTime ()) {
     return {
       data : cache. data ,
       // The data has timed out 
      isOverTime : true ,
    };
  }

  return {
     data : cache. data ,
     // The data does not have a timeout 
    isOverTime : false ,
  };
}

const  getExtraInfo = () => {
   const cacheInfo = getCachedExtraInfo ();
   // The corresponding data will be returned only if the data has not timed out 
  if (cacheInfo && !cacheInfo. isOverTime ) {
       return  Promise . resolve (cacheInfo. data );
  }

  return  getExtraInfoApi (). then ( res => {
     localStorage . setItem ( ` ${EXTRA_INFO_CACHE_KEY} . ${userId} ` , {
       data : res,
       expiredTime : ( new  Data ()). getTime () + CACHE_TIME ,
    });
    return res;
  }). catch ( err => {
     // Only return if there is data, otherwise continue to throw an error 
    if (cacheInfo) {
       return cacheInfo. data ;
    }
    throw err;
  })
}

In this case, we can ensure that the vast majority of users can continue to use it normally. However, if the corresponding interface is unstable, users will have to wait for a long time before they can continue to use it.

At this time, developers can consider abandoning asynchronous code completely and reducing cache time.

const  EXTRA_INFO_CACHE_KEY = 'xxx.xxx.xxx' ;
 // Reduce cache aging to 5 days 
const  CACHE_TIME =   5 * 24 * 60 * 60 * 1000 ;

const  getCachedExtraInfo = () => {
   const cacheStr = localStorage . getItem ( ` ${EXTRA_INFO_CACHE_KEY} . ${userId} ` );

  if (!cacheStr) {
     return  null ;
  }

  let cache = null ;
   try {
    cache = JSON . parse (cacheStr)
  } catch () {
     return  null ;
  }

  if (!cache) {
     return  null ;
  }

  if ((cache. expiredTime ?? 0 ) < new  Date (). getTime ()) {
     return {
       data : cache. data ,
       isOverTime : true ,
    };
  }

  return {
     data : cache. data ,
     isOverTime : false ,
  };
}

const  getExtraInfo = () => {
   const cacheInfo = getCachedExtraInfo ();
   // If it times out, get it and use it next time 
  if (cacheInfo. isOverTime ) {
       getExtraInfoApi (). then ( res => {
         localStorage . setItem ( ` $ {EXTRA_INFO_CACHE_KEY} . ${userId} ` , {
           data : res,
           expiredTime : ( new  Data ()). getTime () + CACHE_TIME ,
        })
      })
  }
  return cacheInfo.data 
}​

Reference documentation

Front-end api request caching solution

Handwriting a front-end storage tool library