Async handling

Introduction

ConanJs gives you a few mechanisms to announce within your actions that you are performing asynchronous operations.

These are the benefits:

  • NO boiler plate code to update your state asynchronously.

  • Automatically updates the Monitor Thread to reflect all the async operations running and their details. Allowing you to subscribe to these updates, to easily add loading screens, block buttons etc.

Check our Todos - Async for an example without boiler plate code and which displays the status of the async operations

  • Allows accessing the underlying ASAPs running at the moment. Note this combined with the ASAPs being cancellable, makes complex scenarios like optimistic updates much simpler

Check our Todos - Optimistic for an example with optimistic updates

Async Actions

There are two mechanisms to declare async actions: auto-bind and monitor actions.

Monitor actions

When you describe your actions, you can access the monitor from the provided thread and the framework will update the Monitor Async thread all the way through the life cycle of the ASAP

return thread.monitor(
    //An Asap
    diContext.issuesService.fetch(repo, org, page).catch(() => thread.reducers.$fetch([])),
    //What to do when the asap resolves
    (issues, reducers) => reducers.$fetch(issues as Issue[]),
    //Next two params are for logging purposes
    // - Description of the async operation
    'fetch',
    // - Payload (also shown on the logging)
    [repo, org, page]
)

Check our Github example to see a monitor example.

Auto-bind

Auto-bind works based on naming convention, it basically builds a monitor automatically for a given reducer given a method that returns an ASAP.

The requirements for autoBind are.

  • The reducer and the action to auto-bind should have matching names (reducer prefixed with a $, action without it)

  • They reducer must take only one parameter.

  • The action to autobind needs to return an ASAP of the same type as the type from reducer parameter.

If all these requirements are met, the action you passed through the autobind will be now accessible as an action for the ConanState and, if you invoke it, in addition to returning the ASAP it will monitor it and invoke the associated reducer.

This is better seen with an example

function updateToAsync (): ASAP<number>{ 
    //This could be any ASAP, in this case we use a dummy delay
    return Asaps.delay (3, 1000);
}

const autoBoundCounter$ = Conan.state ({
    name: 'autoBoundCounter',
    reducers: getState => ({
        $updateTo (newValue) {
            return newValue
        }
    }),
    autoBind: {
        updateTo: updateToAsync
    }
})

// This will update to 5 in 1s
autoBoundCounter$.do.updateTo (5)

You can also see this using a service in this example Todos - Async

The Monitor Thread

The monitor thread contains the updates to the Monitor Info caused by changes in async actions.

The value of the Monitor Info when there are no async actions running is:

{
    inProgressActions: [],
    status: MonitorStatus.IDLE, //'IDLE'
}

This state is reached as soon as you start the Conan State, or as soon as all the async actions running are fulfilled.

There are three properties in the monitorInfo

  • currentAction: This will be populated if there is at least one async action running at the moment, this will contain the representation of the async action that is right now causing an update in the Monitor Thread.

  • status: One of the following

export enum MonitorStatus {
    IDLE = 'IDLE',
    ASYNC_START = "ASYNC_START",
    ASYNC_FULFILLED = "ASYNC_FULFILLED",
    ASYNC_CANCELLED = "ASYNC_CANCELLED",
}
  • inProgressActions: All the actions running at the time that this currentAction is being updated.

Example

A new monitor actions is triggered with name 'updateAsync' and its payload is '5' the Monitor Info would update immediately to:

{
    inProgressActions: [{
        name: 'updateAsync',
        payload: 5,
        asap: [theAsap]
    }],
    currentAction: {
        name: 'updateAsync',
        payload: 5,
        asap: [theAsap]
    },
    status: MonitorStatus.ASYNC_START, //'"ASYNC_START"'
}

If another action is triggered, with the same name, but with a payload of '3', the monitor info would look like:

{
    inProgressActions: [{
        name: 'updateAsync',
        payload: 5,
        asap: [theAsap]
    },{
        name: 'updateAsync',
        payload: 3,
        asap: [theAsap]
    }],
    currentAction: {
        name: 'updateAsync',
        payload: 3,
        asap: [theAsap]
    },
    status: MonitorStatus.ASYNC_START, //'"ASYNC_START"'
}

If the action with payload 3 is resolved successfully:

{
    inProgressActions: [{
        name: 'updateAsync',
        payload: 5,
        asap: [theAsap]
    }],
    currentAction: {
        name: 'updateAsync',
        payload: 3,
        asap: [theAsap]
    },
    status: MonitorStatus.ASYNC_FULFILLED, //'ASYNC_FULFILLED'
}

Then, if the action with payload 5 is resolved, since is the last one, the Monitor Info will update twice.

Once so that we know that the async action has been fulfilled

{
    inProgressActions: [{
        name: 'updateAsync',
        payload: 5,
        asap: [theAsap]
    }],
    currentAction: {
        name: 'updateAsync',
        payload: 5,
        asap: [theAsap]
    },
    status: MonitorStatus.ASYNC_FULFILLED, //'ASYNC_FULFILLED'
}

Once more to let us know that no more async actions are running at the moment

{
    inProgressActions: [],
    status: MonitorStatus.IDLE, //'IDLE'
}

Last updated