Skip to content

Commit 9ca3616

Browse files
committed
Merge pull request #15 from facebook/cachekey
Allow for custom cache key function
2 parents 297a1e5 + 175cb3f commit 9ca3616

File tree

3 files changed

+67
-5
lines changed

3 files changed

+67
-5
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ Create a new `DataLoader` given a batch loading function and options.
151151
- *cache*: Default `true`. Set to `false` to disable caching, instead
152152
creating a new Promise and new key in the `batchLoadFn` for every load.
153153

154+
- *cacheKeyFn*: A function to produce a cache key for a given load key.
155+
Defaults to `key => key`. Useful to provide when JavaScript objects are keys
156+
and two similarly shaped objects should be considered equivalent.
157+
154158
##### `load(key)`
155159

156160
Loads a key, returning a `Promise` for the value represented by that key.

src/__tests__/dataloader-test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,56 @@ describe('Accepts options', () => {
418418
);
419419
});
420420

421+
describe('Accepts object key in custom cacheKey function', () => {
422+
function cacheKey(key) {
423+
var result;
424+
if (typeof key === 'object') {
425+
result = Object.keys(key).sort().map(k => k + ':' + key[k]).join('-');
426+
} else {
427+
result = String(key);
428+
}
429+
return result;
430+
}
431+
432+
it('Accepts objects with simple key', async () => {
433+
var keyA = '1234';
434+
var identityLoadCalls = [];
435+
var identityLoader = new DataLoader(keys => {
436+
identityLoadCalls.push(keys);
437+
return Promise.resolve(keys);
438+
}, { cacheKeyFn: cacheKey });
439+
440+
var valueA = await identityLoader.load(keyA);
441+
expect(valueA).to.equal(keyA);
442+
});
443+
444+
it('Accepts objects with different order of keys', async () => {
445+
var keyA = { a: 123, b: 321 };
446+
var keyB = { b: 321, a: 123 };
447+
448+
var identityLoadCalls = [];
449+
var identityLoader = new DataLoader(keys => {
450+
identityLoadCalls.push(keys);
451+
return Promise.resolve(keys);
452+
}, { cacheKeyFn: cacheKey });
453+
454+
// Fetches as expected
455+
456+
var [ valueA, valueB ] = await Promise.all([
457+
identityLoader.load(keyA),
458+
identityLoader.load(keyB),
459+
]);
460+
461+
expect(valueA).to.equal(keyA);
462+
expect(valueB).to.equal(valueA);
463+
464+
expect(identityLoadCalls).to.have.length(1);
465+
expect(identityLoadCalls[0]).to.have.length(1);
466+
expect(identityLoadCalls[0][0]).to.equal(keyA);
467+
});
468+
469+
});
470+
421471
});
422472

423473
describe('It is resilient to job queue ordering', () => {

src/index.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
// of values or Errors.
1313
type BatchLoadFn<K, V> = (keys: Array<K>) => Promise<Array<V | Error>>
1414

15-
// Optionally turn off batching or caching.
16-
type Options = { batch?: boolean, cache?: boolean }
15+
// Optionally turn off batching or caching or provide a cache key function.
16+
type Options = {
17+
batch?: boolean,
18+
cache?: boolean,
19+
cacheKeyFn?: (key: any) => any
20+
}
1721

1822
/**
1923
* A `DataLoader` creates a public API for loading data from a particular
@@ -63,10 +67,12 @@ export default class DataLoader<K, V> {
6367
var options = this._options;
6468
var shouldBatch = !options || options.batch !== false;
6569
var shouldCache = !options || options.cache !== false;
70+
var cacheKeyFn = options && options.cacheKeyFn;
71+
var cacheKey = cacheKeyFn ? cacheKeyFn(key) : key;
6672

6773
// If caching and there is a cache-hit, return cached Promise.
6874
if (shouldCache) {
69-
var cachedPromise = this._promiseCache.get(key);
75+
var cachedPromise = this._promiseCache.get(cacheKey);
7076
if (cachedPromise) {
7177
return cachedPromise;
7278
}
@@ -93,7 +99,7 @@ export default class DataLoader<K, V> {
9399

94100
// If caching, cache this promise.
95101
if (shouldCache) {
96-
this._promiseCache.set(key, promise);
102+
this._promiseCache.set(cacheKey, promise);
97103
}
98104

99105
return promise;
@@ -127,7 +133,9 @@ export default class DataLoader<K, V> {
127133
* method chaining.
128134
*/
129135
clear(key: K): DataLoader<K, V> {
130-
this._promiseCache.delete(key);
136+
var cacheKeyFn = this._options && this._options.cacheKeyFn;
137+
var cacheKey = cacheKeyFn ? cacheKeyFn(key) : key;
138+
this._promiseCache.delete(cacheKey);
131139
return this;
132140
}
133141

0 commit comments

Comments
 (0)