jhollingworth/marty — pull request #29 by jhollingworth (2014-11-22 18:07:58+00:00)
Title: Store#queryLock

Adding a new function to the store called `query` which is responsible for
* Ensuring only one query for a given key occurs at any one time
* Executing a query against the local cache
* Executing a function to retrieve data if its not in the local cache
* Returning a object, ``StoreQuery`` that represents the lifecycle of the query
```
Store#query(key, localQuery, remoteQuery) -> StoreQuery
```
``StoreQuery`` has a ``status`` which can be loading, successful or failed. Like stores, there is an ``addChangeListener`` which allows you to register a callback when the status of the query changes.

If the ``StoreQuery`` fails, you can see why by accessing ``query.error``. If the query is successful, the result is accessible by accessing ``query.result``.

Only one query per key can be in progress at any one time. While a query for a given key is in progress, invoking ``Store#query`` with that key will return the in progress query. This is to avoid making multiple queries unnecessarily. The key is unlocked once its associated query has completed successfully or failed.

If the local query returns something other than null or undefined then the ``StoreQuery`` moves to the successful state and the result is available in ``StoreQuery#result``. If an error is thrown within the local query, the ``StoreQuery`` moves to the failed state and the thrown error is available in ``StoreQuery#error``

If the local query returns null or undefined then the remote query is executed. Once the remote query completes, the local query is re-executed. If the local query now returns something other that null or undefined then the ``StoreQuery`` moves to the successful state and the result is available in ``StoreQuery#result`. If the local query returns null or undefined then the ``StoreQuery`` moves to the failed state and ``StoreQuery#error`` will return the error "Not found". If an error is thrown within the remote query, the ``StoreQuery`` moves to the failed state and the thrown error is available in ``StoreQuery#error``.

If the remote query returns a promise then the completion of the remote query will be delayed until the promise has resolved.

```
var FooStore = Marty.createStore({
getFoo: function (id) {
return this.query(id, getFromCache, getFromServer);

function getFromCache() {
return this.state.get(id);
}

function getFromServer() {
return FooAPI.getFoo(id);
}
}
});
```

If you are using a state mixin and the state contains a ``StoreQuery``, the mixin will automatically add a change listener and update the components when the query status changes.

To do:
* [ ] Create StoreQuery
* [ ] Add locking to ``Store#query``
* [ ] Add local queries
* [ ] Add remote queries (including returning actions)
* [ ] Documentation