Asynchronous State Management with ActiveJS
Ankit Singh
Posted on November 3, 2020
This article is a follow-up to a previous article about synchronous state management, State Management with a Single Line of Code. I'd suggest that you at least take a quick look at it so that you understand this one better. (It's okay if you don't feel like it, I'll try to keep it simple :)
I created a state-management library called ActiveJS, where async-state-management isn't an afterthought. ActiveJS strives to make state-management simple, and get rid of walls of code required for the current mainstream solutions.
So, without any further adieu, let's get started.
This is what we're going to target and achieve with as little code as possible.
The 4 major aspects of an Async API call:
- Making a Query
- Handling the Response
- Handling the Error
- Checking or listening to the Pending-Status
And then some situational aspects, like:
- Storing the received response
- Sharing the response and status of the API call
- Being able to retry or replay the request
- Clearing the stored error on a successful response, etc.
Now that we know what we need to achieve, all we need is some kind of system that can do all this, where we don't have to set-up all the mechanisms from scratch.
This is all the code we're going to need to achieve all of the above:
import {AsyncSystem} from '@activejs/core'
const asyncSystem = new AsyncSystem()
const {queryUnit, dataUnit, errorUnit, pendingUnit} = asyncSystem
async function fetchAndShare(query) {
try {
const response = await fetch('https://xyz.com/?q=' + query)
const data = await response.json()
dataUnit.dispatch(data)
} catch (err) {
errorUnit.dispatch(err)
}
}
queryUnit.future$.subscribe(query => fetchAndShare(query))
queryUnit.dispatch('some query')
If you don't understand what is going on, that's okay, we'll understand it together, line by line.
The most important part is the AsyncSystem.
import {AsyncSystem} from '@activejs/core';
// initialize an AsyncSystem, ready to receive, store, and share.
const asyncSystem = new AsyncSystem();
AsyncSystem is a systematic combination of 4 separate reactive data structures that it creates internally, called Units, these Units pertain to each major aspect of an async API call namely Query, Data, Error, and Pending-Status.
AsyncSystem also creates some custom relationships among these Units to achieve some of the situational aspects that we mentioned above, these relationships can be enabled or disabled by passing configuration flags to the AsysnSystem.
Extract the data structures for easier access
// using ES6 destructuring assignment
const {queryUnit, dataUnit, errorUnit, pendingUnit} = asyncSystem;
queryUnit
to store, and share the Query, and to trigger the API call
dataUnit
to store, and share the Response data
errorUnit
to store, and share the Error data
pendingUnit
to store, and share the Pending-Status
Setup the data fetching logic using the native fetch
API
// a function to fetch data and disptch the response appropriately
async function fetchAndShare(query) {
try {
// fetch data using fetch API
const response = await fetch('https://xyz.com/?q=' + query);
// extract the JSON data
const data = await response.json();
// dispatch data to the dataUnit
// it also sets the pendingUnit's value to false, automatically
// and, it sets the errorUnit's value to undefined, automatically
dataUnit.dispatch(data);
} catch (err) {
// dispatch error to errorUnit
// it also sets the pendingUnit's value to false, automatically
errorUnit.dispatch(err);
}
}
Setup API request trigger by subscribing to the queryUnit
// whenever a value is dispatched to queryUnit,
// the 'fetchAndShare' will get called
queryUnit.future$.subscribe(query => fetchAndShare(query));
// we can also subscribe to the queryUnit directly, but by using
// future$ we make sure that we start making API calls only after a
// new dispach, otherwise it'd have already made a call.
We can already start listening for the values by subscribing to the reactive data structures we just extracted above.
Listen for values, from anywhere and as many places as needed
// listen for queries
queryUnit.subscribe(query => console.log(query));
// logs undefined immediately and will log future values
// listen for data
dataUnit.subscribe(data => console.log(data));
// logs undefined immediately and will log future values
// listen for errors
errorUnit.subscribe(error => console.log(error));
// logs undefined immediately and will log future values
// listen for pending status
pendingUnit.subscribe(isPending => console.log(isPending));
// logs false immediately and will log future values
All that is left is triggering the API call, which can also be done from anywhere by dispatching a value to the queryUnit
, the rest will be handled by the AsyncSystem and the logic we just wrote.
Trigger an API request
// dispatch a query
// it also sets the pendingUnit's value to true, automatically
queryUnit.dispatch(42)
Retrying/Replaying the last API request
// replay the query
// it also sets the pendingUnit's value to true, automatically
queryUnit.replay()
// it'll re-emit the current query value (i.e. 42 in this case),
// and the rest will work the same as triggering a new API request
That's it, folks, all done.
There are even more things that ActiveJS can do for you very efficiently, but maybe let's discuss that in another article.
Here's a simple StackBlitz Typeahead example built with AsyncSystem and RxJS operators, if you want to try it out yourself.
Here's the visual playground for AsyncSystem, which you can try out without writing any code.
If you reached here,
Please let me know if I added too much information or too little.
Also, let me know what would you like to see ActiveJS do in the next article.
Cheers
🌏 ActiveJS Website
📖 ActiveJS Documentation
🤾♂️ ActiveJS Playground
💻 ActiveJS GitHub Repo (drop a ⭐ maybe :)
Posted on November 3, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.