Skip to content

Instantly share code, notes, and snippets.

@FMCorz
Last active October 24, 2022 06:58
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FMCorz/b5807bec55a1906ab51ac0c7c0a412d0 to your computer and use it in GitHub Desktop.
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.
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 };
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