In the development of web applications, performance is an indispensable topic. For web packaged single page applications, there are many ways to optimize performance, such as tree-shaking, module lazy loading, and using extrens network cdn to accelerate these conventional optimization. Even in the vue-cli project, we can use the -- modern instruction to generate new and old browser code to optimize the program.
In fact, caching must be one of the effective ways to improve web applications, especially when users are limited by network speed. Improve the responsiveness of the system 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.
On the client side, we have many ways to cache data and resources, such as standard browser caching and the current hot Service worker. However, they are more suitable for caching static content. For example, html, js, css and pictures. And caching system data, I use another solution.
Now I will introduce the various api request schemes that I applied to the project, from simple to complex.
Scheme 1 Data Caching
Simple data caching, the first request to obtain data, then use data, no longer request back-end api.
The code is as follows:
const dataCache = new Map() async getWares() { let key = 'wares' // Getting data from the data cache let data = dataCache.get(key) if (!data) { // No Data Request Server const res = await request.get('/getWares') // Other operations ... data = ... // Setting up data cache dataCache.set(key, data) } return data }
The first line of code uses maps with es6 or more. If you don't understand maps well, you can refer to them.
ECMAScript 6 Introduction Set and Map perhaps Exploring ES6 The introduction of map and set can be understood here as a key-value pair storage structure.
Then the code uses the async function, which makes the asynchronous operation more convenient. You can refer to it. ECMAScript 6 Initial async Function To learn or consolidate knowledge.
The code itself is easy to understand. It uses Map objects to cache data, and then calls to fetch data from Map objects. For its simple business scenario, you can use this code directly.
Call method:
getWares().then( ... ) // The second call takes the previous data getWares().then( ... )
Scheme 2 promise cache
The first scheme is not enough in itself. Because if more than two calls to this api are considered at the same time, the second request api will be made because the request is not returned. Of course, if you add a single data source framework like vuex and redux to the system, this problem is not likely to be encountered, but sometimes we want to call APIs for each complex component separately, instead of communicating data between components.
const promiseCache = new Map() getWares() { const key = 'wares' let promise = promiseCache.get(key); // There is no promise in the current promise cache if (!promise) { promise = request.get('/getWares').then(res => { // Operating on res ... }).catch(error => { // After the request comes back, if there is a problem, delete promise from cache to avoid the second request continuing to make an error S promiseCache.delete(key) return Promise.reject(error) }) } // Return promise return promise }
This code avoids the problem of multiple requests at the same time in scheme one. At the same time, promises are deleted in the case of back-end errors, so that there will be no problem that promises with caching errors will always go wrong.
Calling mode:
getWares().then( ... ) // Second call to get previous promise getWares().then( ... )
Scheme 3 Multi-promise Cache
This scheme is to return data simultaneously when more than one api request is needed, if an api error occurs. No correct data is returned.
const querys ={ wares: 'getWares', skus: 'getSku' } const promiseCache = new Map() async queryAll(queryApiName) { // Determine whether the incoming data is an array? const queryIsArray = Array.isArray(queryApiName) // Unified processing of data, whether strings or arrays are treated as arrays const apis = queryIsArray ? queryApiName : [queryApiName] // Get all request services const promiseApi = [] apis.forEach(api => { // Using promise let promise = promiseCache.get(api) if (promise) { // If there is one in the cache, push directly promise.push(promise) } else { promise = request.get(querys[api]).then(res => { // Operating on res ... }).catch(error => { // After the request comes back, if there is a problem, delete promise from cache promiseCache.delete(api) return Promise.reject(error) }) promiseCache.set(api, promise) promiseCache.push(promise) } }) return Promise.all(promiseApi).then(res => { // Returns data based on whether a string or an array is passed in, because it is an array operation itself. // If you pass in a string, you need to take it out return queryIsArray ? res : res[0] }) }
This scheme is a way to obtain data from multiple servers at the same time. Multiple data can be obtained at the same time to operate without errors due to a single data problem.
Calling mode
queryAll('wares').then( ... ) // The second call will not fetch wares, but skus. queryAll(['wares', 'skus']).then( ... )
Scheme 4 adds time-related caching
Often caching is harmful. If we know that the data has been modified, we can delete the cache directly. At this time, we can call the method to request from the server. In this way, we avoid displaying old data on the front end. But we may not operate on the data for a period of time, then the old data will always exist at this time, so we had better set a time to remove the data.
The scheme uses class persistent data as data cache, and adds expired data and parameterization.
The code is as follows:
First, define a persistence class that can store promise or data
class ItemCache() { construct(data, timeout) { this.data = data // Set timeout time, how many seconds this.timeout = timeout // The time when an object is created, approximately set to the time when the data is acquired this.cacheTime = (new Date()).getTime } }
Then we define the data cache. We use the same api as Map
class ExpriesCache { // Define a static data map as a cache pool static cacheMap = new Map() // Is the data timeout static isOverTime(name) { const data = ExpriesCache.cacheMap.get(name) // No data must be timed out if (!data) return true // Get the current timestamp of the system const currentTime = (new Date()).getTime() // Get the past seconds of the current time and storage time const overTime = (currentTime - data.cacheTime) / 1000 // If the number of seconds in the past is greater than the current timeout time, return null and let it go to the server to fetch data. if (Math.abs(overTime) > data.timeout) { // This code may or may not be a problem, but if there is this code, re-entering the method can reduce judgment. ExpriesCache.cacheMap.delete(name) return true } // No timeout return false } // Is the current data timeout in cache static has(name) { return !ExpriesCache.isOverTime(name) } // Delete data from cache static delete(name) { return ExpriesCache.cacheMap.delete(name) } // Obtain static get(name) { const isDataOverTiem = ExpriesCache.isOverTime(name) //If the data timeout, return null, but not ItemCache object return isDataOverTiem ? null : ExpriesCache.cacheMap.get(name).data } // Default storage for 20 minutes static set(name, data, timeout = 1200) { // Setting itemCache const itemCache = mew ItemCache(data, timeout) //cache ExpriesCache.cacheMap.set(name, itemCache) } }
At this point, the data class and the operation class have been defined, which we can define at the api level.
// Generating key value error const generateKeyError = new Error("Can't generate key from name and argument") // Generate key value function generateKey(name, argument) { // Get the data from arguments and turn it into an array const params = Array.from(argument).join(',') try{ // Returns a string, function name + function parameter return `${name}:${params}` }catch(_) { // Returns the generated key error return generateKeyError } } async getWare(params1, params2) { // Generate key const key = generateKey('getWare', [params1, params2]) // Get data let data = ExpriesCache.get(key) if (!data) { const res = await request('/getWares', {params1, params2}) // Using a 10s cache, after 10s, get again to get null and continue requesting from the server ExpriesCache.set(key, res, 10) } return data }
The scheme uses different caching methods with different expiration time and api parameters. Most business scenarios can already be met.
Calling mode
getWares(1,2).then( ... ) // Second call to get previous promise getWares(1,2).then( ... ) // Different parameters, no previous promise getWares(1,3).then( ... )
Scheme 5: Modifier-based scheme 4
It is consistent with the solution of scheme 4, but based on modifier.
The code is as follows:
// Generating key value error const generateKeyError = new Error("Can't generate key from name and argument") // Generate key value function generateKey(name, argument) { // Get the data from arguments and turn it into an array const params = Array.from(argument).join(',') try{ // Return string return `${name}:${params}` }catch(_) { return generateKeyError } } function decorate(handleDescription, entryArgs) { // Determine whether the current final data is descriptor, and if it is descriptor, use it directly? // Modifiers such as log if (isDescriptor(entryArgs[entryArgs.length - 1])) { return handleDescription(...entryArgs, []) } else { // If not // Modifiers such as add(1) plus(20) return function() { return handleDescription(...Array.protptype.slice.call(arguments), entryArgs) } } } function handleApiCache(target, name, descriptor, ...config) { // Get the body of the function and save it const fn = descriptor.value // Modify function body descriptor.value = function () { const key = generateKey(name, arguments) // key cannot be generated, directly requesting server-side data if (key === generateKeyError) { // Make requests using the function body you just saved return fn.apply(null, arguments) } let promise = ExpriesCache.get(key) if (!promise) { // Setting promise promise = fn.apply(null, arguments).catch(error => { // After the request comes back, if there is a problem, delete promise from cache ExpriesCache.delete(key) // Return error return Promise.reject(error) }) // Using a 10s cache, after 10s, get again to get null and continue requesting from the server ExpriesCache.set(key, promise, config[0]) } return promise } return descriptor; } // Develop modifiers function ApiCache(...args) { return decorate(handleApiCache, args) }
At this point, we use classes to cache the api
class Api { // Cache 10s @ApiCache(10) // Do not use the default value at this time, because the current modifier cannot get it. getWare(params1, params2) { return request.get('/getWares') } }
There is no way to use functions as modifiers because functions have function lifts.
For example:
var counter = 0; var add = function () { counter++; }; @add function foo() { }
The code intends to counter equals 1 after execution, but the actual result is counter equals 0. Because of the function elevation, the code actually executed is as follows
@add function foo() { } var counter; var add; counter = 0; add = function () { counter++; };
So there's no way to use modifiers on functions. Specific reference ECMAScript 6 Introduction Decorator
This approach is simple to write and has little impact on the business layer. But caching time cannot be dynamically modified
Call Method
getWares(1,2).then( ... ) // Second call to get previous promise getWares(1,2).then( ... ) // Different parameters, no previous promise getWares(1,3).then( ... )
summary
The caching mechanism and scenarios of api are also basically described here. It can basically complete the vast majority of data business caching. Here I also want to ask you if there is any better solution, or what is wrong in this blog. Welcome to correct, thank you here.
At the same time, there are a lot of unfinished work here, which may continue to improve in the blog in the future.