Skip to content

Commit

Permalink
Merge pull request #17 from facebook/custom-cache
Browse files Browse the repository at this point in the history
Add ability to provide custom cache instance
  • Loading branch information
leebyron committed Jan 12, 2016
2 parents 3670bbb + d2fe461 commit 06a3bf1
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 6 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ Create a new `DataLoader` given a batch loading function and options.
Defaults to `key => key`. Useful to provide when JavaScript objects are keys
and two similarly shaped objects should be considered equivalent.

- *cacheMap*: An instance of [Map][] (or an object with a similar API) to be
used as the underlying cache for this loader. Default `new Map()`.

##### `load(key)`

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


## Custom Caches

DataLoader can optionaly be provided a custom Map instance to use as its
cache. More specifically, any object that implements the methods `get()`,
`set()`, `delete()` and `clear()` can be provided. This allows for custom Maps
which implement various [cache algorithms][] to be provided. By default,
DataLoader uses the standard [Map][] which simply grows until the DataLoader
is released.


## Common Back-ends

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


[@schrockn]: https://github.com/schrockn
[Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
[graphql-js]: https://github.com/graphql/graphql-js
[cache algorithms]: https://en.wikipedia.org/wiki/Cache_algorithms
[express]: http://expressjs.com/
[babel/polyfill]: https://babeljs.io/docs/usage/polyfill/
[node_redis]: https://github.com/NodeRedis/node_redis
Expand Down
74 changes: 74 additions & 0 deletions src/__tests__/dataloader-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,80 @@ describe('Accepts options', () => {

});

describe('Accepts custom cacheMap instance', () => {

class SimpleMap {
stash: Object;

constructor() {
this.stash = {};
}
get(key) {
return this.stash[key];
}
set(key, value) {
this.stash[key] = value;
}
delete(key) {
delete this.stash[key];
}
clear() {
this.stash = {};
}
}

it('Accepts a custom cache map implementation', async () => {
var aCustomMap = new SimpleMap();
var identityLoadCalls = [];
var identityLoader = new DataLoader(keys => {
identityLoadCalls.push(keys);
return Promise.resolve(keys);
}, { cacheMap: aCustomMap });

// Fetches as expected

var [ valueA, valueB1 ] = await Promise.all([
identityLoader.load('a'),
identityLoader.load('b'),
]);

expect(valueA).to.equal('a');
expect(valueB1).to.equal('b');

expect(identityLoadCalls).to.deep.equal([ [ 'a', 'b' ] ]);
expect(Object.keys(aCustomMap.stash)).to.deep.equal([ 'a', 'b' ]);

var [ valueC, valueB2 ] = await Promise.all([
identityLoader.load('c'),
identityLoader.load('b'),
]);

expect(valueC).to.equal('c');
expect(valueB2).to.equal('b');

expect(identityLoadCalls).to.deep.equal([ [ 'a', 'b' ], [ 'c' ] ]);
expect(Object.keys(aCustomMap.stash)).to.deep.equal([ 'a', 'b', 'c' ]);

// Supports clear

identityLoader.clear('b');
var valueB3 = await identityLoader.load('b');

expect(valueB3).to.equal('b');
expect(identityLoadCalls).to.deep.equal(
[ [ 'a', 'b' ], [ 'c' ], [ 'b' ] ]
);
expect(Object.keys(aCustomMap.stash)).to.deep.equal([ 'a', 'c', 'b' ]);

// Supports clear all

identityLoader.clearAll();

expect(Object.keys(aCustomMap.stash)).to.deep.equal([]);
});

});

});

describe('It is resilient to job queue ordering', () => {
Expand Down
21 changes: 15 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@
// of values or Errors.
type BatchLoadFn<K, V> = (keys: Array<K>) => Promise<Array<V | Error>>

// Optionally turn off batching or caching or provide a cache key function.
type Options = {
type CacheMap<K, V> = {
get(key: K): V | void;
set(key: K, value: V): any;
delete(key: K): any;
clear(): any;
}

// Optionally turn off batching or caching or provide a cache key function or a
// custom cache instance.
type Options<K, V> = {
batch?: boolean,
cache?: boolean,
cacheMap?: CacheMap<K, Promise<V>>,
cacheKeyFn?: (key: any) => any
}

Expand All @@ -32,7 +41,7 @@ type Options = {
export default class DataLoader<K, V> {
constructor(
batchLoadFn: BatchLoadFn<K, V>,
options?: Options
options?: Options<K, V>
) {
if (typeof batchLoadFn !== 'function') {
throw new TypeError(
Expand All @@ -42,14 +51,14 @@ export default class DataLoader<K, V> {
}
this._batchLoadFn = batchLoadFn;
this._options = options;
this._promiseCache = new Map();
this._promiseCache = options && options.cacheMap || new Map();
this._queue = [];
}

// Private
_batchLoadFn: BatchLoadFn<K, V>;
_options: ?Options;
_promiseCache: Map<K, Promise<V>>;
_options: ?Options<K, V>;
_promiseCache: CacheMap<K, Promise<V>>;
_queue: LoaderQueue<K, V>;

/**
Expand Down

0 comments on commit 06a3bf1

Please sign in to comment.