Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <gerwitzm@lovullo.com>2016-12-30 13:58:06 -0500
committerMike Gerwitz <gerwitzm@lovullo.com>2017-01-04 16:17:35 -0500
commit0be39adfdb2ebe7afbb963a2180294b1f62bd57d (patch)
treef1de66a79fbd78d8eaf2c05f46e7fa53ca95e107
parentacc75cc3a92d639daefb5043ecd1e0d6a070e800 (diff)
downloadliza-0be39adfdb2ebe7afbb963a2180294b1f62bd57d.tar.gz
liza-0be39adfdb2ebe7afbb963a2180294b1f62bd57d.tar.bz2
liza-0be39adfdb2ebe7afbb963a2180294b1f62bd57d.zip
Make {,Memory}Store asynchronous
This isn't terribly useful as a general-purpose cache if it can't handle async requests.
-rw-r--r--src/store/Cascading.js54
-rw-r--r--src/store/MemoryStore.js92
-rw-r--r--test/store/CascadingTest.js69
-rw-r--r--test/store/MemoryStoreTest.js66
4 files changed, 195 insertions, 86 deletions
diff --git a/src/store/Cascading.js b/src/store/Cascading.js
index 0bf4dfa..5e6c55d 100644
--- a/src/store/Cascading.js
+++ b/src/store/Cascading.js
@@ -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;
+ } );
+ } );
},
} );
diff --git a/src/store/MemoryStore.js b/src/store/MemoryStore.js
index 74bfbdf..52786f4 100644
--- a/src/store/MemoryStore.js
+++ b/src/store/MemoryStore.js
@@ -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`
+ *
+ * The promise will be rejected if the key is unavailable.
*
- * @param {string} key cache key
+ * @param {string} key store key
*
- * @return {*} `key` value
+ * @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 )
+ );
}
} );
diff --git a/test/store/CascadingTest.js b/test/store/CascadingTest.js
index 51aaca0..737ca76 100644
--- a/test/store/CascadingTest.js
+++ b/test/store/CascadingTest.js
@@ -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 ) );
+ } );
} );
} );
} ); \ No newline at end of file
diff --git a/test/store/MemoryStoreTest.js b/test/store/MemoryStoreTest.js
index a152911..82392af 100644
--- a/test/store/MemoryStoreTest.js
+++ b/test/store/MemoryStoreTest.js
@@ -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 );
} );
} );
} );