Long running tasks in the UI are for the most part an antipattern. A user will never want to wait for that to complete and if they don’t know it is happening they are likely to close the tab. In general, actions can be decomposed into:
- Calling a service to set some data
- Get back an update
- Propagate those updates to the necessary components on the UI to have the shadow DOM update.
This may be done via a timer
but it would never be long running, it would be a quick action executed once every minute/every hour, but that’s it. With the ability to chain actions together, the better approach would be to create small functions which each do one thing well and then push them to a queue. However even in that case, it isn’t functionality that a UI should be implementing since it is unlikely the user expects that experience.
If there really long running activities, you might be missing the help of a second self-contained microservice which can track and provide RESTful resources. When it concerns needing real time updates to a UI via event triggers. A websocket or PWA service worker is the preferred route. A service would directly send a message from the service to the PWA instance with an update message instructing it to fetch again the service data.
Another consideration is that most browsers will terminate any script that is long running (for some definition of long running). It is neither a good experience for a user nor expected to be utilized in web frameworks. So it would be good to consider a different pattern.
I’ll provide a simple example of code which exemplifies the pattern I’ve shown above. Here is the created
function from Teaminator’s Health Screen. We load a lot of data from different services to display a summary result. When the main component is created, this happens:
async created() {
const summaryPromise = this.reportManager.getTeamIndicators(); if (this.overallScore === null && !this.uncompletedHealthCheck) {
reportManager.getSettings().then(settings => {
this.healthCheckSettings = {
dayOfWeek: settings.dayOfWeek,
frequency: settings.frequency,
loading: false
};
}, error => {
this.logger.warn({ title: 'Failed to get health check configuration', exception: error });
});
} await summaryPromise;
if (this.healthCheckData?.lastUpdated) {
this.suggestionsManager.fetchSuggestions();
} this.isLoading = false;
}
If you are unfamiliar with code that looks like this, it is the created
function offered by Vue. In here is a bunch of dynamic loads, with optional awaiting
depending on the exact state of the system. It doesn’t matter if the promises are frozen because the component is unloaded, and I definitely don’t need control over them.
There is only one place in the app which some sort of async processing is used other than usual async user actions to update the UI, and that is in logging. Logging is just a matter of stacking events in a queue and sending a batch of them when it reaches a limit or a timer ticks (which ever happens first).