Last active
October 24, 2022 06:58
-
-
Save FMCorz/b5807bec55a1906ab51ac0c7c0a412d0 to your computer and use it in GitHub Desktop.
Axios with a custom cache adapter. It handles setting cache to groups to permit invalidating a bunch at once, and it returns cache data when there is an error with the request.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Axios from 'axios'; | |
import { setupCache } from 'axios-cache-adapter'; | |
import localforage from 'localforage'; | |
import find from 'lodash/find'; | |
import isEmpty from 'lodash/isEmpty'; | |
const CACHE_MAX_AGE = 2 * 60 * 60 * 1000; | |
// Extracting 'axios-cache-adapter/src/exclude' as importing it leads to webpack not compiling it. | |
function exclude(config = {}, req) { | |
const { exclude = {}, debug } = config; | |
if (typeof exclude.filter === 'function' && exclude.filter(req)) { | |
debug(`Excluding request by filter ${req.url}`); | |
return true; | |
} | |
// do not cache request with query | |
const hasQueryParams = req.url.match(/\?.*$/) || !isEmpty(req.params); | |
if (exclude.query && hasQueryParams) { | |
debug(`Excluding request by query ${req.url}`); | |
return true; | |
} | |
const paths = exclude.paths || []; | |
const found = find(paths, regexp => req.url.match(regexp)); | |
if (found) { | |
debug(`Excluding request by url match ${req.url}`); | |
return true; | |
} | |
return false; | |
} | |
// Create a store. | |
const cacheStore = localforage.createInstance({ name: 'branchups-project' }); | |
// Define the cache adapter. | |
const cacheAdapter = setupCache({ | |
clearOnStale: false, | |
debug: false, | |
exclude: { | |
filter: req => { | |
return req.cache && req.cache.exclude; | |
} | |
}, | |
key: req => { | |
return (req.cache && req.cache.key) || req.url; | |
}, | |
maxAge: CACHE_MAX_AGE, | |
store: cacheStore | |
}); | |
const getKey = cacheAdapter.config.key; | |
const debug = cacheAdapter.config.debug; | |
// Our adapter factory which handles network errors, and groups. | |
const myAdapter = function(adapter) { | |
return async function(req) { | |
const isExcluded = exclude(cacheAdapter.config, req); | |
const key = getKey(req); | |
// Add the key to the groups. | |
if (!isExcluded && req.cache && req.cache.groups) { | |
const groupsCacheKey = '__groups'; | |
const groupsKeys = (await cacheStore.getItem(groupsCacheKey)) || {}; | |
let hasSetAny = false; | |
// Loop over each group. | |
for (let group of req.cache.groups) { | |
if (!(group in groupsKeys)) { | |
groupsKeys[group] = []; | |
} | |
if (groupsKeys[group].indexOf(key) < 0) { | |
hasSetAny = true; | |
groupsKeys[group].push(key); | |
} | |
} | |
// Commit the changes. | |
if (hasSetAny) { | |
await cacheStore.setItem(groupsCacheKey, groupsKeys); | |
} | |
} | |
let res; | |
try { | |
res = await adapter(req); | |
} catch (e) { | |
debug('request-failed', req.url); | |
if (e.request && (req.cache && req.cache.useOnNetworkError) && !isExcluded) { | |
// Mimic the behaviour of axios-cache-adapter, but directly get from store. | |
res = await cacheStore.getItem(key); | |
if (res && res.data) { | |
res = res.data; | |
res.config = req; | |
res.request = { | |
networkError: true, | |
fromCache: true | |
}; | |
return res; | |
} | |
} | |
throw e; | |
} | |
return res; | |
}; | |
}; | |
const axios = Axios.create({ | |
// ... SNIP ... | |
// The cache adapter. | |
adapter: myAdapter(cacheAdapter.adapter), | |
cache: { | |
key: null, | |
useOnNetworkError: true | |
} | |
}); | |
const get = async function(url, config) { | |
return axios.get(url, config); | |
}; | |
const clearCacheByKey = async function(key) { | |
console.log('Clearing cache by key: ' + key); | |
let result = await cacheStore.getItem(key); | |
if (result && 'expires' in result) { | |
result.expires = 1; | |
await cacheStore.setItem(key, result); | |
} | |
}; | |
const clearCacheByGroup = async function(group) { | |
console.log('Clearing cache by group: ' + group); | |
const groups = (await cacheStore.getItem('__groups')) || {}; | |
const keys = groups[group] || []; | |
for (let key of keys) { | |
await clearCacheByKey(key); | |
} | |
}; | |
const clearCacheByGroups = function(groups) { | |
return Promise.all(groups.map(clearCacheByGroup)); | |
}; | |
const purgeCache = async function() { | |
console.log('Clearing all caches'); | |
await cacheStore.clear(); | |
}; | |
export default { get, post: axios.post, clearCacheByKey, clearCacheByGroup, clearCacheByGroups, purgeCache }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests from './requests'; | |
const getMembers = function() { | |
let url = '/api/v1/members'; | |
return requests | |
.get(url, { | |
cache: { | |
key: 'members-list', | |
groups: ['members'] | |
} | |
}); | |
}; | |
const getMemberStuff = function(id) { | |
let url = `/api/v1/member/${id}/stuff`; | |
return requests | |
.get(url, { | |
cache: { | |
groups: ['members'] | |
} | |
}); | |
}; | |
const invalidateMembersCache = async function() { | |
return Promise.all([ | |
requests.clearCacheByKey('members-list'), | |
requests.clearCacheByGroups(['members']) | |
]); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment