Custom AsyncSystem
ActiveJS exposes an AsyncSystemBase class that can be used to create customized AsyncSystem like Systems.
One limitation of AsyncSystem is that it doesn't let you change the type of Units used by it. With AsyncSystemBase you can create your own AsyncSystem like Systems and use any type of Unit you want as queryUnit, dataUnit and errorUnit.
AsyncSystemBase can be utilized in two ways,

1. Direct Usage

Instead of creating an instance of AsyncSystem where the Units are created internally, you can create the Units yourself and pass them to the AsyncSystemBase directly.
1
const customAsyncSystem = new AsyncSystemBase(
2
new ListUnit<string>(), // queryUnit
3
new DictUnit<{ a: number, b: string }>(), // dataUnit
4
new GenericUnit<number>(), // errorUnit
5
new BoolUnit(), // pendingUnit
6
{clearErrorOnData: false} // config is optional
7
);
8
​
9
// extract the Units from the System
10
const {queryUnit, dataUnit, errorUnit, pendingUnit} = customAsyncSystem;
11
​
12
// confirm the type of Units
13
queryUnit instanceof ListUnit // true
14
dataUnit instanceof DictUnit // true
15
errorUnit instanceof GenericUnit // true
16
pendingUnit instanceof BoolUnit // true
Copied!

2. Extend the AsyncSystemBase class

If you don't want to create Units every time you want to create a custom AsyncSystem, you can extend the base class and do the same thing that AsyncSystem does, abstract away the Unit creation in the extended class and only accept typings through generics. This will allow you to be more efficient while still staying flexible.
Example: Create a custom AsyncSystem that uses ListUnit as queryUnit, DictUnit as dataUnit and GenericUnit as errorUnit.

Implementation:

1
// accept the types for queryUnit, dataUnit and erroUnit as generics
2
export class CustomAsyncSystem<Query, Data, Error>
3
​
4
// extend the base class and pass the Units with types as generics
5
extends AsyncSystemBase<ListUnit<Query>, DictUnit<Data>, GenericUnit<Error>> {
6
​
7
// accept config options optionally to pass them to the base class
8
constructor(config?: AsyncSystemBaseConfig<Query, Data, Error>) {
9
10
// create the Units and pass them to the base class along with the config
11
super(
12
new ListUnit(),
13
new DictUnit(),
14
new GenericUnit(),
15
new BoolUnit(),
16
config
17
);
18
}
19
}
Copied!

Usage:

1
type QueryType = number; // type for queryUnit's value
2
type DataType = string; // type for dataUnit's value
3
type ErrorType = {status: number, message: string}; // type for errorUnit's value
4
​
5
// initialize a CustomSyncSystem, pass the types as generics
6
const customSystem = new CustomAsyncSystem<QueryType, DataType, ErrorType>();
7
// that's it
8
​
9
// checking the types of Units
10
queryUnit instanceof ListUnit // true
11
dataUnit instanceof DictUnit // true
12
errorUnit instanceof GenericUnit // true
13
pendingUnit instanceof BoolUnit // true
Copied!

Notes

When using a custom AsyncSystem, directly or by extending, there are a few things that you should keep in mind, to get the expected results.
​AsyncSystem uses GenericUnit because it's very permissive in what it accepts as its value while other Units only allow a specific data type, this difference in behavior might cause a hung state, for example, if we use a DictUnit as the dataUnit and we try to dispatch an array value, it will get ignored and any observer expecting a new value wouldn't receive it. Additionally, pendingUnit wouldn't be updated automatically. (i.e. It'll stay true until successful data or error dispatch)
To mitigate this problem, when using a non-GenericUnit as a dataUnit or errorUnit, ensure that the value being dispatched is valid and will be dispatched. Every Unit has a wouldDispatch method for exactly this kind of scenario.