Make {,Memory}Store asynchronous
This isn't terribly useful as a general-purpose cache if it can't handle async requests.master
parent
acc75cc3a9
commit
0be39adfdb
|
@ -32,22 +32,23 @@ var Trait = require( 'easejs' ).Trait,
|
|||
* when `#clear` is invoked on `S`.
|
||||
*
|
||||
* @example
|
||||
* let store_a = Store().add( 'key', value' ),
|
||||
* store_b = Store().add( 'foo', 'bar' );
|
||||
* let store_a = Store(),
|
||||
* store_b = Store();
|
||||
*
|
||||
* store_a.get( 'key' ); // value
|
||||
* store_b.get( 'foo' ); // bar
|
||||
* // assuming sync. store for example (ignore promises)
|
||||
* store_a.add( 'key', 'value' );
|
||||
* store_b.add( 'foo', 'bar' );
|
||||
*
|
||||
* Store.use( Cascading )
|
||||
* .add( 'a', store_a )
|
||||
* .add( 'b', store_b )
|
||||
* .clear();
|
||||
* let container = Store.use( Cascading );
|
||||
* container.add( 'a', store_a );
|
||||
* container.add( 'b', store_b );
|
||||
* container.clear();
|
||||
*
|
||||
* store_a.get( 'key' ); // undefined
|
||||
* store_b.get( 'foo' ); // undefined
|
||||
* store_a.get( 'key' ); // Promise rejects
|
||||
* store_b.get( 'foo' ); // Promise rejects
|
||||
*
|
||||
* Store.use( Cascading ).add( 'invalid', 'value' );
|
||||
* // TypeError: Can only add Store to Cascading stores
|
||||
* // rejected with TypeError: Can only add Store to Cascading stores
|
||||
*
|
||||
* Although clear cascades to each `Store`, other methods do not (for
|
||||
* example, `get` will not query all `Store`s); another trait should
|
||||
|
@ -71,13 +72,15 @@ module.exports = Trait( 'Cascading' )
|
|||
* @param {string} key store key
|
||||
* @param {Store} value Store to attach
|
||||
*
|
||||
* @return {Store} self
|
||||
* @return {Promise} promise to add item to store
|
||||
*/
|
||||
'virtual abstract override public add': function( key, value )
|
||||
{
|
||||
if ( !Class.isA( Store, value ) )
|
||||
{
|
||||
throw TypeError( "Can only add Store to Cascading stores" );
|
||||
return Promise.reject(
|
||||
TypeError( "Can only add Store to Cascading stores" )
|
||||
);
|
||||
}
|
||||
|
||||
return this.__super( key, value );
|
||||
|
@ -87,13 +90,28 @@ module.exports = Trait( 'Cascading' )
|
|||
/**
|
||||
* Clear all stores in the store
|
||||
*
|
||||
* @return {Store} self
|
||||
* @return {Promise} promise to clear all caches
|
||||
*/
|
||||
'virtual abstract override public clear': function()
|
||||
{
|
||||
this.reduce( function( _, store )
|
||||
{
|
||||
store.clear();
|
||||
} );
|
||||
return this.reduce(
|
||||
function( accum, store )
|
||||
{
|
||||
accum.push( store.clear() );
|
||||
return accum;
|
||||
},
|
||||
[]
|
||||
)
|
||||
.then( function( promises )
|
||||
{
|
||||
return Promise.all( promises );
|
||||
} )
|
||||
.then( function( result )
|
||||
{
|
||||
return result.every( function( value )
|
||||
{
|
||||
return value === true;
|
||||
} );
|
||||
} );
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -36,14 +36,28 @@ var Class = require( 'easejs' ).Class,
|
|||
* @example
|
||||
* let s = MemoryStore();
|
||||
*
|
||||
* s.add( 'foo', 'bar' );
|
||||
* s.add( 'baz', 'quux' );
|
||||
* s.get( 'foo' ); // bar
|
||||
* s.get( 'baz' ); // quux
|
||||
* Promise.all( [
|
||||
* s.add( 'foo', 'bar' ),
|
||||
* s.add( 'baz', 'quux' ),
|
||||
* ] )
|
||||
* .then( Promise.all( [
|
||||
* {
|
||||
* s.get( 'foo' ),
|
||||
* s.get( 'baz' ),
|
||||
* ] ) } )
|
||||
* .then( function( values )
|
||||
* {
|
||||
* // values = [ 'bar', 'quux' ]
|
||||
* } );
|
||||
*
|
||||
* s.clear();
|
||||
* s.get( 'foo' ); // undefined
|
||||
* s.get( 'baz' ); // undefined
|
||||
* s.clear().then( function()
|
||||
* {
|
||||
* s.get( 'foo' )
|
||||
* .catch( function()
|
||||
* {
|
||||
* // foo is no longer defined
|
||||
* } );
|
||||
* } );
|
||||
*/
|
||||
module.exports = Class( 'MemoryStore' )
|
||||
.implement( Store )
|
||||
|
@ -58,51 +72,62 @@ module.exports = Class( 'MemoryStore' )
|
|||
|
||||
|
||||
/**
|
||||
* Add item to cache under `key` with value `value`
|
||||
* Add item to store under `key` with value `value`
|
||||
*
|
||||
* @param {string} key cache key
|
||||
* The promise will be fulfilled with an object containing the
|
||||
* `key` and `value` added to the store; this is convenient for
|
||||
* promises.
|
||||
*
|
||||
* @param {string} key store key
|
||||
* @param {*} value value for key
|
||||
*
|
||||
* @return {Store} self
|
||||
* @return {Promise} promise to add item to store
|
||||
*/
|
||||
'virtual public add': function( key, value )
|
||||
{
|
||||
this._store[ key ] = value;
|
||||
|
||||
return this;
|
||||
return Promise.resolve( {
|
||||
key: key,
|
||||
value: value,
|
||||
} );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve item from cache under `key`
|
||||
* Retrieve item from store under `key`
|
||||
*
|
||||
* @param {string} key cache key
|
||||
* The promise will be rejected if the key is unavailable.
|
||||
*
|
||||
* @return {*} `key` value
|
||||
* @param {string} key store key
|
||||
*
|
||||
* @return {Promise} promise for the key value
|
||||
*/
|
||||
'virtual public get': function( key )
|
||||
{
|
||||
return this._store[ key ];
|
||||
return ( this._store[ key ] !== undefined )
|
||||
? Promise.resolve( this._store[ key ] )
|
||||
: Promise.reject( 'Key ' + key + ' does not exist' );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Clear all items in cache
|
||||
* Clear all items in store
|
||||
*
|
||||
* @return {Store} self
|
||||
* @return {Promise} promise to clear store
|
||||
*/
|
||||
'virtual public clear': function()
|
||||
{
|
||||
this._store = {};
|
||||
|
||||
return this;
|
||||
return Promise.resolve( true );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Fold (reduce) all cached values
|
||||
* Fold (reduce) all stored values
|
||||
*
|
||||
* This provides a way to iterate through all cached values and
|
||||
* This provides a way to iterate through all stored values and
|
||||
* their keys while providing a useful functional result (folding).
|
||||
*
|
||||
* The order of folding is undefined.
|
||||
|
@ -110,25 +135,30 @@ module.exports = Class( 'MemoryStore' )
|
|||
* The ternary function `callback` is of the same form as
|
||||
* {@link Array#fold}: the first argument is the value of the
|
||||
* accumulator (initialized to the value of `initial`; the second
|
||||
* is the cached item; and the third is the key of that item.
|
||||
* is the stored item; and the third is the key of that item.
|
||||
*
|
||||
* Warning: if a subtype or mixin has an intensive store lookup
|
||||
* operating, reducing could take some time.
|
||||
*
|
||||
* @param {function(*,*,string=)} callback folding function
|
||||
* @param {*} initial initial value for accumulator
|
||||
*
|
||||
* @return {*} folded value (final accumulator value)
|
||||
* @return {Promise} promise of a folded value (final accumulator value)
|
||||
*/
|
||||
'public reduce': function( callback, initial )
|
||||
{
|
||||
var store = this._store;
|
||||
|
||||
return Object.keys( store )
|
||||
.map( function( key )
|
||||
{
|
||||
return [ key, store[ key ] ];
|
||||
} )
|
||||
.reduce( function( accum, values )
|
||||
{
|
||||
return callback( accum, values[ 1 ], values[ 0 ] );
|
||||
}, initial );
|
||||
return Promise.resolve(
|
||||
Object.keys( store )
|
||||
.map( function( key )
|
||||
{
|
||||
return [ key, store[ key ] ];
|
||||
} )
|
||||
.reduce( function( accum, values )
|
||||
{
|
||||
return callback( accum, values[ 1 ], values[ 0 ] );
|
||||
}, initial )
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -22,25 +22,28 @@
|
|||
"use strict";
|
||||
|
||||
var store = require( '../../' ).store,
|
||||
expect = require( 'chai' ).expect,
|
||||
chai = require( 'chai' ),
|
||||
expect = chai.expect,
|
||||
Store = store.MemoryStore,
|
||||
Sut = store.Cascading;
|
||||
|
||||
chai.use( require( 'chai-as-promised' ) );
|
||||
|
||||
|
||||
describe( 'store.Cascading', () =>
|
||||
{
|
||||
describe( '#add', () =>
|
||||
{
|
||||
it( 'does not allow attaching non-store objects', () =>
|
||||
{
|
||||
expect( () => Store.use( Sut )().add( 'invalid', {} ) )
|
||||
.to.throw( TypeError );
|
||||
expect( Store.use( Sut )().add( 'invalid', {} ) )
|
||||
.to.be.rejectedWith( TypeError );
|
||||
} );
|
||||
|
||||
|
||||
it( 'allows attaching Store objects', () =>
|
||||
{
|
||||
expect( () => Store.use( Sut )().add( 'valid', Store() ) )
|
||||
.to.not.throw( TypeError );
|
||||
return Store.use( Sut )().add( 'valid', Store() );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
@ -56,6 +59,8 @@ describe( 'store.Cascading', () =>
|
|||
'override clear'()
|
||||
{
|
||||
cleared.push( this.__inst );
|
||||
|
||||
return Promise.resolve( true );
|
||||
}
|
||||
} );
|
||||
|
||||
|
@ -65,13 +70,55 @@ describe( 'store.Cascading', () =>
|
|||
stores.forEach( ( store, i ) => sut.add( i, store ) );
|
||||
|
||||
// should trigger clear on all stores
|
||||
sut.clear();
|
||||
return sut.clear()
|
||||
.then( () =>
|
||||
{
|
||||
expect(
|
||||
stores.every( store =>
|
||||
cleared.some( item => item === store )
|
||||
)
|
||||
).to.be.true;
|
||||
} );
|
||||
} );
|
||||
|
||||
expect(
|
||||
stores.every( store =>
|
||||
cleared.some( item => item === store )
|
||||
)
|
||||
).to.be.true;
|
||||
|
||||
[
|
||||
[ [ true, true, true ], true ],
|
||||
[ [ true, true, false ], false ],
|
||||
[ [ false, true, true ], false ],
|
||||
[ [ false, false, false ], false ],
|
||||
].forEach( testdata =>
|
||||
{
|
||||
let clears = testdata[ 0 ],
|
||||
expected = testdata[ 1 ];
|
||||
|
||||
it( 'fails if any store fails to clear', () =>
|
||||
{
|
||||
let StubStore = Store.extend(
|
||||
{
|
||||
_result: false,
|
||||
|
||||
__construct( result )
|
||||
{
|
||||
this._result = result;
|
||||
},
|
||||
|
||||
'override clear'()
|
||||
{
|
||||
return Promise.resolve( this._result );
|
||||
},
|
||||
} );
|
||||
|
||||
let sut = Store.use( Sut )();
|
||||
|
||||
clears.forEach( ( result, i ) =>
|
||||
{
|
||||
sut.add( i, StubStore( result ) );
|
||||
} );
|
||||
|
||||
return sut.clear()
|
||||
.then( result => expect( result ).to.equal( expected ) );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -21,11 +21,14 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var store = require( '../../' ).store,
|
||||
expect = require( 'chai' ).expect,
|
||||
Class = require( 'easejs' ).Class,
|
||||
Trait = require( 'easejs' ).Trait,
|
||||
Sut = store.MemoryStore;
|
||||
var store = require( '../../' ).store,
|
||||
chai = require( 'chai' ),
|
||||
expect = chai.expect,
|
||||
Class = require( 'easejs' ).Class,
|
||||
Trait = require( 'easejs' ).Trait,
|
||||
Sut = store.MemoryStore;
|
||||
|
||||
chai.use( require( 'chai-as-promised' ) );
|
||||
|
||||
|
||||
describe( 'store.MemoryStore', () =>
|
||||
|
@ -37,10 +40,10 @@ describe( 'store.MemoryStore', () =>
|
|||
const sut = Sut();
|
||||
const item = {};
|
||||
|
||||
expect(
|
||||
return expect(
|
||||
sut.add( 'foo', item )
|
||||
.get( 'foo' )
|
||||
).to.equal( item );
|
||||
.then( () => sut.get( 'foo' ) )
|
||||
).to.eventually.equal( item );
|
||||
} );
|
||||
|
||||
|
||||
|
@ -49,11 +52,22 @@ describe( 'store.MemoryStore', () =>
|
|||
const sut = Sut();
|
||||
const item = {};
|
||||
|
||||
expect(
|
||||
return expect(
|
||||
sut.add( 'foo', [] )
|
||||
.add( 'foo', item )
|
||||
.get( 'foo' )
|
||||
).to.equal( item );
|
||||
.then( () => sut.add( 'foo', item ) )
|
||||
.then( () => sut.get( 'foo' ) )
|
||||
).to.eventually.equal( item );
|
||||
} );
|
||||
|
||||
|
||||
it( 'provides the key and value of the added item', () =>
|
||||
{
|
||||
const key = 'key';
|
||||
const value = 'val';
|
||||
|
||||
return expect(
|
||||
Sut().add( key, value )
|
||||
).to.eventually.deep.equal( { key: key, value: value } );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
@ -61,9 +75,10 @@ describe( 'store.MemoryStore', () =>
|
|||
// most things implicitly tested above
|
||||
describe( '#get', () =>
|
||||
{
|
||||
it( 'returns undefined if store item does not exist', () =>
|
||||
it( 'rejects promise if store item does not exist', () =>
|
||||
{
|
||||
expect( Sut().get( 'unknown' ) ).to.be.undefined;
|
||||
return expect( Sut().get( 'unknown' ) )
|
||||
.to.eventually.be.rejected;
|
||||
} );
|
||||
} );
|
||||
|
||||
|
@ -78,17 +93,15 @@ describe( 'store.MemoryStore', () =>
|
|||
keys.forEach( key => sut.add( key ) );
|
||||
|
||||
// should remove all items
|
||||
sut.clear();
|
||||
|
||||
keys.forEach( key => expect( sut.get( key ) ).to.be.undefined );
|
||||
} );
|
||||
|
||||
|
||||
it( 'returns self', () =>
|
||||
{
|
||||
const sut = Sut();
|
||||
|
||||
expect( sut.clear() ).to.equal( sut );
|
||||
return sut.clear().then( () =>
|
||||
{
|
||||
return Promise.all(
|
||||
keys.map( key => {
|
||||
expect( sut.get( key ) )
|
||||
.to.eventually.be.rejected
|
||||
} )
|
||||
);
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
@ -179,7 +192,8 @@ describe( 'store.MemoryStore', () =>
|
|||
);
|
||||
|
||||
// implicitly tests initial
|
||||
expect( sut.sum() ).to.equal( 11 );
|
||||
return expect( sut.sum() )
|
||||
to.equal( 11 );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
Loading…
Reference in New Issue