data-with-cache
Implementing data caching in JS applications can make them more responsive for users, and also more resilient against a poor network connection or API problems.
The goal of this library is to provide a configurable wrapper for API calls that provides caching logic, to save you writing this yourself :)
Example below, and check out the demo page.
function getCompany(companyId: number) {
return new DataWithCache<Company>({
strategy: 'cache_first',
cache: appCache,
objectType: 'company',
objectId: String(companyId),
cacheExpires: 60000, // 1 minute
getData: () => api.getCompany(companyId)
});
}
const company = await getCompany(123).getData();
Currently Included Strategies
API First
- Try the API first and wait until
apiTimeout
, or until an error occurs - Return API data if successful, and add it to the cache
- If API throws an error or times out, then return the data from the cache (if present)
- If no matching data in the cache then throw an error
This strategy is good for when displaying up-to-date information is critical, but you want your app to keep working offline or with an unreliable / slow network
Cache First - (stale-while-revalidate)
- Return the data from the cache first (if present)
- If there is no matching data in the cache, try the API
- If the API call is successful, return the data and add it to the cache
- If the cached data is older than
cacheExpires
ms, contact the API asynchronously and get the most up-to-date data
This strategy is best if you want to ensure your UI is rendered as quickly as possible, and you are OK with your users occasionally seeing slightly out-of-date information.
With this strategy, you can still update your UI as soon as the updated information is
received from your API, via the onRefreshed()
event handler.
Supported Backends
- In Memory
- IndexedDB
- Any cache that can be accessed via the ICacheBacked interface
Recent Changes
See the CHANGELOG
Examples
API First
In the example below, we create a utility function that can be called from anywhere
in our application to fetch data. Behind the scenes it will automatically return the data from
cache if the API does not respond within apiTimeout
milliseconds or if the API
responds with an error.
import { DataWithCache, IndexedDBCache } from 'data-with-cache';
import * as api from './api';
const cache = new IndexedDBCache('app_cache');
export function getSeminarAttendees(seminarId: number) {
return new DataWithCache<api.IAttendee[]>({
strategy: 'api_first',
cache,
objectType: 'seminarAttendees',
objectId: String(seminarId),
apiTimeout: 5000,
getData: () => api.getSeminarAttendees(seminarId)
});
}
// Somwehere else in your application, you can get the data like this:
(async () => {
const seminarId = 123;
const attendees = await getSeminarAttendees(seminarId).getData();
console.log('Attendees:', attendees);
})();
Cache First
In the example below, we create a utility function that can be called from anywhere in our application to fetch data. If there is matching data in the cache, it will be returned immediately and displayed to the user.
If the cached data is older than cacheExpires
milliseconds then DataWithCache will
fetch the most up-to-date data asynchronously. When this happens, the
onRefreshing()
callback will be called, which you can use to show a "refreshing"
spinner to the user to indicate the displayed data may be updated shortly.
When the refresh is completed the onRefreshed()
callback will be called with the
most recent data, which is then displayed in the UI.
import { DataWithCache, IndexedDBCache } from 'data-with-cache';
import * as api from './api';
const cache = new IndexedDBCache('app_cache');
export function getSeminarAttendees(seminarId: number) {
return new DataWithCache<api.IAttendee[]>({
strategy: 'cache_first',
cache,
objectType: 'seminarAttendees',
objectId: String(seminarId),
cacheExpires: 300000, // 5 minutes
getData: () => api.getSeminarAttendees(seminarId)
});
}
// Somwehere else in your application, you can get the data like this:
(async () => {
const seminarId = 123;
const attendeesQuery = getSeminarAttendees(seminarId);
// Register event handlers so we can update our UI if data
// is being refreshed asynchronously.
attendeesQuery.onRefreshing = () => {
ui.showRefreshingIndicator(true);
}
attendeesQuery.onRefreshed = (data: api.IAttendees[]) => {
ui.showRefreshingIndicator(false);
ui.showAttendeeData(data);
}
// getData() will return immediately if there is a cache match
attendeesData = await attendeesQuery.getData();
ui.showAttendeeData(attendeesData);
})();
API
DataWithCache
parameters
const dataWithCache = new DataWithCache({ ...parameters });
Required Parameters:
strategy:
- eitherapi_first
orcache_first
cache:
- a JavaScript object that implements the ICacheBacked interfaceobjectType:
- a string representing the type of data you are caching (e.g.company
orregion
)objectId:
- a unique ID for the data being cached. Used in combination withobjectType
to query the cachegetData:
- a JavaScriptfunction
that returns aPromise
that either resolves with the required data from your data source, or rejects with an error.
Optional Parameters:
apiTimeout:
- Time (in milliseconds) to wait for thegetData()
function to resolve. Default is 5 seconds.cacheTimeout:
- Time (in milliseconds) to wait for the cache to return a result. Default is 1 second.cacheExpires:
- Max age (in milliseconds) of a cached record, before it needs to be refreshed. If it is not set, cache entries never expire. Set to0
to expire the cache immediately. Default is not set.onError:
- A function with the signature(error: Error, level: 'warning' | 'error') => void
which is called with the details of any errors returned by thegetData()
function and thecache
.debug:
- Set totrue
to enable verbose console output from the cache functions.
DataWithCache
properties & methods
const dataWithCache = new DataWithCache({ ...parameters });
// These event handlers need to be set before calling getData()
dataWithCache.onRefreshing = () => {
console.log('A refresh is happening asynchronously...');
}
dataWithCache.onRefreshed = (data) => {
console.log('A refresh completed. The new data is:', data);
}
const data = await dataWithCache.getData(); // Returns data (either from cache or API)
Contributing
PRs welcome!
License
MIT