Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Last active August 29, 2015 14:17
Show Gist options
  • Save mattmccray/1d511ba1ee3affd01b7e to your computer and use it in GitHub Desktop.
Save mattmccray/1d511ba1ee3affd01b7e to your computer and use it in GitHub Desktop.

Revisions

  1. mattmccray revised this gist Mar 18, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion async-data-fetching.md
    Original file line number Diff line number Diff line change
    @@ -94,7 +94,7 @@ In the application, I treat `undefined` and `null` differently. If a value is `u

    ---

    Just for completeness, here's the implementation my `FetchStore`. It's not plug-n-play because I have a class (`Resource`) that wraps the CRUD url generation and xhr calling. But it'd be easy to hook up with raw xhr calls.
    Just for completeness, here's the implementation of my `FetchStore`. It's not plug-n-play because I have a class (`Resource`) that wraps the CRUD url generation and xhr calling. But it'd be easy to hook up with raw xhr calls.

    ```javascript
    import alt from './alt'
  2. mattmccray revised this gist Mar 18, 2015. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions async-data-fetching.md
    Original file line number Diff line number Diff line change
    @@ -122,7 +122,7 @@ class FetchStore {
    enqueued= this.queue[ token],
    api= Resource.type( type)

    if( enqueued ) {
    if( enqueued) {
    // Request already sent...
    if( callback) {
    // Add this callback to the other queued callbacks
    @@ -145,7 +145,7 @@ class FetchStore {
    FetchActions.resourceFetched({ type, query, token, data})
    }
    else {
    FetchActions.resourcenUnfetched({ type, query, token, data})
    FetchActions.resourceFetchFailure({ type, query, token, data})
    }

    info.callbacks
  3. mattmccray revised this gist Mar 18, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion async-data-fetching.md
    Original file line number Diff line number Diff line change
    @@ -94,7 +94,7 @@ In the application, I treat `undefined` and `null` differently. If a value is `u

    ---

    Just for completeness, here's my implementation my `FetchStore`. It's not plug-n-play because I have a class (`Resource`) that wraps the CRUD url generation and xhr calling. But it'd be easy to hook up with raw xhr calls.
    Just for completeness, here's the implementation my `FetchStore`. It's not plug-n-play because I have a class (`Resource`) that wraps the CRUD url generation and xhr calling. But it'd be easy to hook up with raw xhr calls.

    ```javascript
    import alt from './alt'
  4. mattmccray revised this gist Mar 18, 2015. 1 changed file with 83 additions and 1 deletion.
    84 changes: 83 additions & 1 deletion async-data-fetching.md
    Original file line number Diff line number Diff line change
    @@ -29,7 +29,7 @@ class FetchActions {
    }

    // Primarily used in resolving data pre-route rendering,
    // this returns a Promise that resolves once the resource
    // this returns a Promise that resolves once the resource(s)
    // have been loaded and processed through the dispatcher.
    retrieveResource( type, query) {
    return new Promise(( resolve, reject) => {
    @@ -91,3 +91,85 @@ export default alt.createStore( CompanyStore, 'CompanyStore')
    ```

    In the application, I treat `undefined` and `null` differently. If a value is `undefined` it's unfetched data, if `null` then it's been fetched with no results

    ---

    Just for completeness, here's my implementation my `FetchStore`. It's not plug-n-play because I have a class (`Resource`) that wraps the CRUD url generation and xhr calling. But it'd be easy to hook up with raw xhr calls.

    ```javascript
    import alt from './alt'
    import FetchActions from './FetchActions'
    import {Resource} from 'toolkit'

    class FetchStore {

    constructor() {
    this.bindActions( FetchActions)

    this.queue= {}
    }

    onLoadResource({ type, query }) {
    this._loadResource( type, query)
    }

    onRetrieveResource({ type, query, callback }) {
    this._loadResource( type, query, callback)
    }

    _loadResource( type, query, callback) {
    const token= this._tokenize( type, query),
    enqueued= this.queue[ token],
    api= Resource.type( type)

    if( enqueued ) {
    // Request already sent...
    if( callback) {
    // Add this callback to the other queued callbacks
    enqueued.callbacks.push( callback)
    }
    return
    }

    this.queue[ token]= defaultState()

    const apiCall= (Type.isObject( query) ? api.find( query) : api.get( query))
    .then( this._resourceResponse.bind( this, type, query, token, true))
    .catch( this._resourceResponse.bind( this, type, query, token, false))
    }

    _resourceResponse( type, query, token, success, data) {
    const info= this.queue[ token]

    if( success) {
    FetchActions.resourceFetched({ type, query, token, data})
    }
    else {
    FetchActions.resourcenUnfetched({ type, query, token, data})
    }

    info.callbacks
    .forEach( callback => {
    if( success) callback( null, data)
    else callback( data)
    })

    delete this.queue[ token]
    }

    _tokenize( type, query) {
    return JSON.stringify({ type, query})
    }
    }

    export default alt.createStore( FetchStore, 'FetchStore')

    function defaultState() {
    return {
    callbacks: [],
    error: null,
    finish: null,
    start: new Date()
    }
    }
    ```
  5. mattmccray created this gist Mar 17, 2015.
    93 changes: 93 additions & 0 deletions async-data-fetching.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,93 @@
    # Async Data Fetching

    Consider it a given that this talks to a RESTful API (simple crud and, in my case, predicate filtering).

    There is a `FetchStore` that manages the API calls, ensuring there aren't duplicate calls, resolving promises once data arrives, etc. The store itself is rather opaque. It doesn't have any public accessors.

    The `FetchActions` defines two actions for clients to call, and two for other stores to consume in the dispatch cycle:

    ```javascript
    import alt from './alt'

    class FetchActions {

    constructor() {
    this.generateActions(
    // Stores listen for this, payload contains resource type,
    // query, and response data.
    'resourceFetched',
    // Same, except contains error information
    'resourceFetchFailure'
    )
    }

    // A 'fire and forget' method used by other stores. Just
    // signals that data needs to be fetched for this type
    // and query (usually an id).
    loadResource( type, query) {
    this.dispatch({ type, query })
    }

    // Primarily used in resolving data pre-route rendering,
    // this returns a Promise that resolves once the resource
    // have been loaded and processed through the dispatcher.
    retrieveResource( type, query) {
    return new Promise(( resolve, reject) => {
    function callback( err, data) {
    if( err) reject( err)
    else resolve( data)
    }
    this.dispatch({ type, query, callback })
    })
    }

    }

    export default alt.createActions( FetchActions)
    ```

    That's basically it. Internally, the `FetchStore` keeps track of pending requests, and any associated callbacks, resolving them once the `xhr` returns.

    Here's an example of how it's used by other Stores:

    ```javascript
    import alt from './alt'
    import CompanyActions from './CompanyActions'
    import FetchActions from './FetchActions'

    const COMPANY_TYPE= "Company"

    class CompanyStore {

    constructor() {
    this.bindActions( CompanyActions)
    this.bindActions( FetchActions)

    this.idmap= {}
    }

    onResourceFetched({ type, query, data }) {
    if( type === COMPANY_TYPE) {
    this.idmap[ data.companyId]= data
    }
    else {
    return false
    }
    }

    static get( id) {
    const company= this.getState().idmap[ id]

    if( Type.isUndefined( company)) {
    FetchActions.loadResource( COMPANY_TYPE, id)
    }

    return company
    }

    }

    export default alt.createStore( CompanyStore, 'CompanyStore')
    ```

    In the application, I treat `undefined` and `null` differently. If a value is `undefined` it's unfetched data, if `null` then it's been fetched with no results