feat: Add error handling for inMemoryCache
This commit is contained in:
committed by
mrugesh mohapatra
parent
09cb38aa21
commit
15a9992603
@ -1,6 +1,7 @@
|
|||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
|
||||||
|
import { reportError } from '../middlewares/error-reporter';
|
||||||
import InMemoryCache from '../utils/in-memory-cache';
|
import InMemoryCache from '../utils/in-memory-cache';
|
||||||
|
|
||||||
const log = debug('fcc:boot:donate');
|
const log = debug('fcc:boot:donate');
|
||||||
@ -9,7 +10,7 @@ const fiveMinutes = 1000 * 60 * 5;
|
|||||||
export default function(Donation) {
|
export default function(Donation) {
|
||||||
let activeDonationUpdateInterval = null;
|
let activeDonationUpdateInterval = null;
|
||||||
const activeDonationCountCacheTTL = fiveMinutes;
|
const activeDonationCountCacheTTL = fiveMinutes;
|
||||||
const activeDonationCountCache = InMemoryCache(0);
|
const activeDonationCountCache = InMemoryCache(0, reportError);
|
||||||
const activeDonationsQuery$ = () =>
|
const activeDonationsQuery$ = () =>
|
||||||
Donation.find$({
|
Donation.find$({
|
||||||
// eslint-disable-next-line no-undefined
|
// eslint-disable-next-line no-undefined
|
||||||
@ -23,25 +24,50 @@ export default function(Donation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
process.on('exit', cleanUp);
|
process.on('exit', cleanUp);
|
||||||
|
|
||||||
Donation.on('dataSourceAttached', () => {
|
Donation.on('dataSourceAttached', () => {
|
||||||
Donation.find$ = Observable.fromNodeCallback(Donation.find.bind(Donation));
|
Donation.find$ = Observable.fromNodeCallback(Donation.find.bind(Donation));
|
||||||
Donation.findOne$ = Observable.fromNodeCallback(
|
Donation.findOne$ = Observable.fromNodeCallback(
|
||||||
Donation.findOne.bind(Donation)
|
Donation.findOne.bind(Donation)
|
||||||
);
|
);
|
||||||
activeDonationsQuery$().subscribe(count => {
|
|
||||||
log('activeDonator count: %d', count);
|
|
||||||
return activeDonationCountCache.update(() => count);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
seedTheCache()
|
||||||
|
.then(setupCacheUpdateInterval)
|
||||||
|
.catch(err => {
|
||||||
|
const errMsg = `Error caught seeding the cache: ${err.message}`;
|
||||||
|
err.message = errMsg;
|
||||||
|
reportError(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function seedTheCache() {
|
||||||
|
return new Promise((resolve, reject) =>
|
||||||
|
Observable.defer(activeDonationsQuery$).subscribe(count => {
|
||||||
|
log('activeDonator count: %d', count);
|
||||||
|
activeDonationCountCache.update(() => count);
|
||||||
|
return resolve();
|
||||||
|
}, reject)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupCacheUpdateInterval() {
|
||||||
activeDonationUpdateInterval = setInterval(
|
activeDonationUpdateInterval = setInterval(
|
||||||
() =>
|
() =>
|
||||||
activeDonationsQuery$().subscribe(count => {
|
Observable.defer(activeDonationsQuery$).subscribe(
|
||||||
log('activeDonator count: %d', count);
|
count => {
|
||||||
return activeDonationCountCache.update(() => count);
|
log('activeDonator count: %d', count);
|
||||||
}),
|
return activeDonationCountCache.update(() => count);
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
const errMsg = `Error caught updating the cache: ${err.message}`;
|
||||||
|
err.message = errMsg;
|
||||||
|
reportError(err);
|
||||||
|
}
|
||||||
|
),
|
||||||
activeDonationCountCacheTTL
|
activeDonationCountCacheTTL
|
||||||
);
|
);
|
||||||
});
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentActiveDonationCount$() {
|
function getCurrentActiveDonationCount$() {
|
||||||
return Observable.of(activeDonationCountCache.get());
|
return Observable.of(activeDonationCountCache.get());
|
||||||
|
@ -2,21 +2,32 @@ function isPromiseLike(thing) {
|
|||||||
return !!thing && typeof thing.then === 'function';
|
return !!thing && typeof thing.then === 'function';
|
||||||
}
|
}
|
||||||
|
|
||||||
function InMemoryCache(initialValue) {
|
function InMemoryCache(initialValue, reportError) {
|
||||||
|
if (typeof reportError !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
'No reportError function specified for this in-memory-cache'
|
||||||
|
);
|
||||||
|
}
|
||||||
const cacheKey = Symbol('cacheKey');
|
const cacheKey = Symbol('cacheKey');
|
||||||
const cache = new Map();
|
const cache = new Map();
|
||||||
|
cache.set(cacheKey, initialValue);
|
||||||
if (typeof initialValue !== 'undefined') {
|
|
||||||
cache.set(cacheKey, initialValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get() {
|
get() {
|
||||||
const value = cache.get(cacheKey);
|
const value = cache.get(cacheKey);
|
||||||
return typeof value !== 'undefined' ? value : null;
|
return typeof value !== 'undefined' ? value : null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async update(fn) {
|
async update(fn) {
|
||||||
const maybePromisedValue = fn();
|
let maybePromisedValue;
|
||||||
|
try {
|
||||||
|
maybePromisedValue = fn();
|
||||||
|
} catch (e) {
|
||||||
|
const errMsg = `InMemoryCache > update > caught: ${e.message}`;
|
||||||
|
e.message = errMsg;
|
||||||
|
reportError(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (isPromiseLike(maybePromisedValue)) {
|
if (isPromiseLike(maybePromisedValue)) {
|
||||||
return maybePromisedValue.then(value => cache.set(cacheKey, value));
|
return maybePromisedValue.then(value => cache.set(cacheKey, value));
|
||||||
} else {
|
} else {
|
||||||
@ -25,6 +36,7 @@ function InMemoryCache(initialValue) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
return cache.delete(cacheKey);
|
return cache.delete(cacheKey);
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,40 @@
|
|||||||
/* global describe expect it beforeEach */
|
/* global describe expect it */
|
||||||
import inMemoryCache from './in-memory-cache';
|
import inMemoryCache from './in-memory-cache';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
describe('InMemoryCache', () => {
|
describe('InMemoryCache', () => {
|
||||||
|
let reportErrorStub;
|
||||||
const theAnswer = 42;
|
const theAnswer = 42;
|
||||||
const before = 'before';
|
const before = 'before';
|
||||||
const after = 'after';
|
const after = 'after';
|
||||||
const emptyCacheValue = null;
|
const emptyCacheValue = null;
|
||||||
|
|
||||||
describe('get', () => {
|
beforeEach(() => {
|
||||||
it('returns null for an empty cache', () => {
|
reportErrorStub = sinon.spy();
|
||||||
const cache = inMemoryCache();
|
});
|
||||||
expect(cache.get()).toBe(emptyCacheValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
it('throws if no report function is passed as a second argument', () => {
|
||||||
|
expect(() => inMemoryCache(null)).toThrowError(
|
||||||
|
'No reportError function specified for this in-memory-cache'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get', () => {
|
||||||
it('returns an initial value', () => {
|
it('returns an initial value', () => {
|
||||||
const cache = inMemoryCache(theAnswer);
|
const cache = inMemoryCache(theAnswer, reportErrorStub);
|
||||||
expect(cache.get()).toBe(theAnswer);
|
expect(cache.get()).toBe(theAnswer);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
it('updates the cached value', () => {
|
it('updates the cached value', () => {
|
||||||
const cache = inMemoryCache(before);
|
const cache = inMemoryCache(before, reportErrorStub);
|
||||||
cache.update(() => after);
|
cache.update(() => after);
|
||||||
expect(cache.get()).toBe(after);
|
expect(cache.get()).toBe(after);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle promises correctly', done => {
|
it('can handle promises correctly', done => {
|
||||||
const cache = inMemoryCache(before);
|
const cache = inMemoryCache(before, reportErrorStub);
|
||||||
cache.update(() => new Promise(resolve => resolve(after)));
|
cache.update(() => new Promise(resolve => resolve(after)));
|
||||||
// because async
|
// because async
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
@ -35,12 +42,25 @@ describe('InMemoryCache', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reports errors thrown from the update function', () => {
|
||||||
|
const reportErrorStub = sinon.spy();
|
||||||
|
const cache = inMemoryCache(before, reportErrorStub);
|
||||||
|
|
||||||
|
const updateError = new Error('An update error');
|
||||||
|
const updateThatThrows = () => {
|
||||||
|
throw updateError;
|
||||||
|
};
|
||||||
|
|
||||||
|
cache.update(updateThatThrows);
|
||||||
|
expect(reportErrorStub.calledWith(updateError)).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('clear', () => {
|
describe('clear', () => {
|
||||||
it('clears the cache', () => {
|
it('clears the cache', () => {
|
||||||
expect.assertions(2);
|
expect.assertions(2);
|
||||||
const cache = inMemoryCache(theAnswer);
|
const cache = inMemoryCache(theAnswer, reportErrorStub);
|
||||||
expect(cache.get()).toBe(theAnswer);
|
expect(cache.get()).toBe(theAnswer);
|
||||||
cache.clear();
|
cache.clear();
|
||||||
expect(cache.get()).toBe(emptyCacheValue);
|
expect(cache.get()).toBe(emptyCacheValue);
|
||||||
|
Reference in New Issue
Block a user