5 minutes read
The "hypereact" state manager supports asynchronous programming through promises. Specifically the features that can be used with async/await based pattern are:
The "dispatch" can be invoked with an action object/instance or a Promise that will resolve to an action. If a Promise is detected the dispatch function returns a Promise itself thus allowing to await in async functions.
// simulate an asynchronous function that returns a result in the future
const delay = (result: any = null): Promise<any> => {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(result);
}, 100);
});
};
// assuming store manager has been coonfigured (example from the getting started code)
const storeManager = StoreManager.getInstance();
(async () => {
// dispatch a generic Redux action
const jsonAction = {
type: "UNMANAGED_ACTION"
};
await storeManager.dispatch(delay(jsonAction));
// dispatch a class typed action (example from the getting started code)
const action = new SetExampleMessageAction("...");
await storeManager.dispatch(delay(action));
})();
If the action passed as Promise is rejected, the "dispatch" will reject consistently the returned Promise. In the async/await pattern the exception of the async action generator is propagated by the dispatch function and can be catched as usual.
The re-hydration of state slices might require data fetching from the server or other asynchronous tasks to be executed before having the full data to be dispatched. Because the process of re-initializing the state should occur before the state is "ready", the store manager of "hypereact" provides two main features to handle such scenarios:
This feature is implemented through an automated two-steps state flow that leverage an internal lazy-action dispatch with type prefixed with "..." that is described as follows for the store initialization:
Because the "rehydrate" functions should never throw any exception but should handle eventual issues internally. In the asynchronous scenario, the store manager does handle the promise rejection keeping the coded initial state as a fallback.
// simulate an asynchronous function that returns a result in the future
const sleep = (ms: number, result: any = null): Promise<any> => {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(result);
}, ms);
});
};
interface UserState {
authenticated: boolean;
details: {
name?: string
}
}
interface PersistedUserState {
authenticated: boolean;
}
const initialState: UserState = {
authenticated: false,
details: {}
};
// simulate a function that is getting user details (name) from server
// assume details are returned based on the authentication tokens/cookies
// and not expired tokens throw make request fail and throw expection
const fetchUserName = async () => {
await sleep(2000);
return "John";
};
// custom persistent reduceable reducer with asynchronous rehydration
export class UserReducer extends PersistentReduceableReducer<UserState> {
// for security/privacy we only store latest authentication status (not details)
dehydrate(state: UserState) : PersistedUserState {
return { authenticated: state.authenticated };
}
// rehydration includes user details fetching from the server if he/she was authenticated
async rehydrate(state: UserState, data: PersistedUserState): Promise<UserState> {
// if user was authenticated
if (data.authenticated) {
try {
const name = await fetchUserName();
return {
authenticated: true,
details: {
name
}
};
} catch(e) {
// tokens/cookies expired or not anymore valid
}
}
// initial (not authenticated user) state
return state;
}
}
The asynchronous support introduces the need to get the "readiness" of the root state and/or specific state slices. A state (root or slice) is "ready" when all the eventual slices are not handled by lazy-rehydrating reducers or eventual lazy-rehydration is not needed (no persisted state) or finished (successfully or not).
The library provides:
Both the function accepts a slice key as argument to get or wait for readiness against a specific state slice and reducer re-hydration.
// assuming the store manager has been configured with a lazy-rehydrating reducer
const storeManager = StoreManager.getInstance();
// check if store state is ready (and all slices)
if (storeManager.isReady()) { console.log("root state is ready"); }
// check if the needed slice state is ready
if (storeManager.isReady("user")) { console.log("user state slice is ready"); }
// wait until entire store state is ready
(async () -> {
await storeManager.waitUntilReady();
console.log("do whatever you need to do")
});
// wait until the needed slice state is ready
(async () -> {
await storeManager.waitUntilReady("user");
console.log("do whatever you need to do now that the user state slice is ready")
});