Skip to content

Commit 06a3bf1

Browse files
committed
Merge pull request #17 from facebook/custom-cache
Add ability to provide custom cache instance
2 parents 3670bbb + d2fe461 commit 06a3bf1

File tree

3 files changed

+104
-6
lines changed

3 files changed

+104
-6
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ Create a new `DataLoader` given a batch loading function and options.
155155
Defaults to `key => key`. Useful to provide when JavaScript objects are keys
156156
and two similarly shaped objects should be considered equivalent.
157157

158+
- *cacheMap*: An instance of [Map][] (or an object with a similar API) to be
159+
used as the underlying cache for this loader. Default `new Map()`.
160+
158161
##### `load(key)`
159162

160163
Loads a key, returning a `Promise` for the value represented by that key.
@@ -279,6 +282,16 @@ This provides a single value to pass around to code which needs to perform
279282
data loading, such as part of the `rootValue` in a [graphql-js][] request.
280283

281284

285+
## Custom Caches
286+
287+
DataLoader can optionaly be provided a custom Map instance to use as its
288+
cache. More specifically, any object that implements the methods `get()`,
289+
`set()`, `delete()` and `clear()` can be provided. This allows for custom Maps
290+
which implement various [cache algorithms][] to be provided. By default,
291+
DataLoader uses the standard [Map][] which simply grows until the DataLoader
292+
is released.
293+
294+
282295
## Common Back-ends
283296

284297
Looking to get started with a specific back-end? Try these example loaders:
@@ -397,7 +410,9 @@ Promise.all([ promise1, promise2 ]).then(([ user1, user2]) => {
397410

398411

399412
[@schrockn]: https://github.com/schrockn
413+
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
400414
[graphql-js]: https://github.com/graphql/graphql-js
415+
[cache algorithms]: https://en.wikipedia.org/wiki/Cache_algorithms
401416
[express]: http://expressjs.com/
402417
[babel/polyfill]: https://babeljs.io/docs/usage/polyfill/
403418
[node_redis]: https://github.com/NodeRedis/node_redis

src/__tests__/dataloader-test.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,80 @@ describe('Accepts options', () => {
468468

469469
});
470470

471+
describe('Accepts custom cacheMap instance', () => {
472+
473+
class SimpleMap {
474+
stash: Object;
475+
476+
constructor() {
477+
this.stash = {};
478+
}
479+
get(key) {
480+
return this.stash[key];
481+
}
482+
set(key, value) {
483+
this.stash[key] = value;
484+
}
485+
delete(key) {
486+
delete this.stash[key];
487+
}
488+
clear() {
489+
this.stash = {};
490+
}
491+
}
492+
493+
it('Accepts a custom cache map implementation', async () => {
494+
var aCustomMap = new SimpleMap();
495+
var identityLoadCalls = [];
496+
var identityLoader = new DataLoader(keys => {
497+
identityLoadCalls.push(keys);
498+
return Promise.resolve(keys);
499+
}, { cacheMap: aCustomMap });
500+
501+
// Fetches as expected
502+
503+
var [ valueA, valueB1 ] = await Promise.all([
504+
identityLoader.load('a'),
505+
identityLoader.load('b'),
506+
]);
507+
508+
expect(valueA).to.equal('a');
509+
expect(valueB1).to.equal('b');
510+
511+
expect(identityLoadCalls).to.deep.equal([ [ 'a', 'b' ] ]);
512+
expect(Object.keys(aCustomMap.stash)).to.deep.equal([ 'a', 'b' ]);
513+
514+
var [ valueC, valueB2 ] = await Promise.all([
515+
identityLoader.load('c'),
516+
identityLoader.load('b'),
517+
]);
518+
519+
expect(valueC).to.equal('c');
520+
expect(valueB2).to.equal('b');
521+
522+
expect(identityLoadCalls).to.deep.equal([ [ 'a', 'b' ], [ 'c' ] ]);
523+
expect(Object.keys(aCustomMap.stash)).to.deep.equal([ 'a', 'b', 'c' ]);
524+
525+
// Supports clear
526+
527+
identityLoader.clear('b');
528+
var valueB3 = await identityLoader.load('b');
529+
530+
expect(valueB3).to.equal('b');
531+
expect(identityLoadCalls).to.deep.equal(
532+
[ [ 'a', 'b' ], [ 'c' ], [ 'b' ] ]
533+
);
534+
expect(Object.keys(aCustomMap.stash)).to.deep.equal([ 'a', 'c', 'b' ]);
535+
536+
// Supports clear all
537+
538+
identityLoader.clearAll();
539+
540+
expect(Object.keys(aCustomMap.stash)).to.deep.equal([]);
541+
});
542+
543+
});
544+
471545
});
472546

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

src/index.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@
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 or provide a cache key function.
16-
type Options = {
15+
type CacheMap<K, V> = {
16+
get(key: K): V | void;
17+
set(key: K, value: V): any;
18+
delete(key: K): any;
19+
clear(): any;
20+
}
21+
22+
// Optionally turn off batching or caching or provide a cache key function or a
23+
// custom cache instance.
24+
type Options<K, V> = {
1725
batch?: boolean,
1826
cache?: boolean,
27+
cacheMap?: CacheMap<K, Promise<V>>,
1928
cacheKeyFn?: (key: any) => any
2029
}
2130

@@ -32,7 +41,7 @@ type Options = {
3241
export default class DataLoader<K, V> {
3342
constructor(
3443
batchLoadFn: BatchLoadFn<K, V>,
35-
options?: Options
44+
options?: Options<K, V>
3645
) {
3746
if (typeof batchLoadFn !== 'function') {
3847
throw new TypeError(
@@ -42,14 +51,14 @@ export default class DataLoader<K, V> {
4251
}
4352
this._batchLoadFn = batchLoadFn;
4453
this._options = options;
45-
this._promiseCache = new Map();
54+
this._promiseCache = options && options.cacheMap || new Map();
4655
this._queue = [];
4756
}
4857

4958
// Private
5059
_batchLoadFn: BatchLoadFn<K, V>;
51-
_options: ?Options;
52-
_promiseCache: Map<K, Promise<V>>;
60+
_options: ?Options<K, V>;
61+
_promiseCache: CacheMap<K, Promise<V>>;
5362
_queue: LoaderQueue<K, V>;
5463

5564
/**

0 commit comments

Comments
 (0)