service
Part of the 𝗥𝘅𝑓𝑥 family of libraries.
30 years after setTimeout
introduced the world to the asynchronous nature of JavaScript, effect execution is still clumsy at best, and broken in many cases. And none of the popular front-end solutions (Angular, React, RxJS) present a complete solution that deals with all the concerns of async effects in a framework-independent way.
loading
/active
state tracking.started
,next
,complete
,error
, canceled
, etc.)An 𝗥𝘅𝑓𝑥 service (or a bus) is a view-framework-independent, pure JS container for Effect Management and State Mangement, based on RxJS. An 𝗥𝘅𝑓𝑥 Service supports all the above pain points in an easy API.
loading
state fields which must be set and unset manuallyuseEffect
, async pipe) to manage asynchrony.useEffect
being used often in the wrong ways.In short - if you believe there is a more concise, more airtight, race-condition-proof way to do async, you may have found it right here in an 𝗥𝘅𝑓𝑥 service or bus listener.
This will create a counter that incremements after 1000 msec, queueing up future increments if one is already in progres.
import { createQueueingService } from '@rxfx/service'
const initialState = 0
const asyncCounter = createQueueingService<void, void, Error, number>(
'count',
() => after(1000),
({ isResponse }) => (state = initialState, event) => {
if (isResponse(event)) {
return state + 1
}
return state;
}
);
Although there are APIs for framework integration (see @rxfx/react
), in pure JS the service is used like this:
// Request a counter increment (will complete after 1000 msec)
asyncCounter.request();
// Request a counter increment, with a Promise for when done
asyncCounter.send().then(() => /* ... */)
// Cancel any current executions of the effect, and/or queued
asyncCounter.cancelCurrent()
asyncCounter.cancelCurrentAndQueued()
// Update your UI state
asyncCounter.state.subscribe(count => /* ... */)
// Update your loading indicator
asyncCounter.isActive.subscribe(isActive => /* ... */)
// Display an error which is self-clearing
asyncCounter.currentError.subscribe(e => /* ... */)
// Consume returned values, or progress notifications of the effect
asyncCounter.responses.subscribe(resp => /* ... */)
Because RxFx is RxJS, it is easy to integrate into Angular. You'll be happy to no longer have to track loading
variables in state, and have a simpler API to both cancelation and concurrency.
A component need only import (or inject) a service, bus, or effect, and it can expose properties and methods to a template trivially:
import { counterService } from '../services/counter.service';
@Component({
selector: 'app-my-counter',
templateUrl: './my-counter.component.html',
})
export class MyCounterComponent {
count$: Observable<number>;
isActive$: Observable<boolean>;
constructor() {
this.count$ = counterService.state;
this.isActive$ = counterService.isActive;
}
increment() {
counterService.request(1);
}
decrement() {
counterService.request(-1);
}
reset() {
counterService.reset();
}
}
Play with the CodeSandbox.
The following examples and tutorials will give you a feel for what UX you can build, and the ease of DX you'll find.
It doesn't take more than a simple time-delayed async counter to show all the subtleties of async, and how 𝗥𝘅𝑓𝑥 lets you control them:
Synchronous Counter - By having an empty effect, and a reducer that increments the count upon a request, this synchronous service has the exact same architecture as an asynchronous one.
Asynchronous Counter with loading and cancelability, default concurrency. This service shows a loading state, and allows cancelation. It allows multiple increments concurrently, and the reducer increments on the response side of the delay.
Asynchronous Counter, queued, with cancel-on-unmount By simply modifying createService
to createQueueingService
, this service guarantees no more than one increment is in progress at a time. On unmount, all current and queued increments are canceled.
Asynchronous Counter, with progress indicator. By wrapping the handler in monitorHandler
, we can update the UI with the percent completed, without affecting what's been written previously. Note: this is a compatibility-approach for when an effect doesn't provide its own progress updates - for example any Promise returning function
Asynchronous Counter, with cancelable fetch Instead of a mere delay, this service awaits a real fetch
to a delayed endpoint at httpbin.org. It uses makeAbortableHandler
to obtain and pass a signal
that will fully cancel the fetch when the service is told to cancel.
The counter is one example of the 7 GUIs benchmark for building UI applications. In the 7 GUIs, different frameworks can be compared against how elegantly they solve the building of 7 GUIs of increasing complexity.
𝗥𝘅𝑓𝑥 has been used to build all 7 GUIs, the counter being the first one. The remaining, with links to live versions are:
Race conditions are easily prevented when code is set to run in the correct Concurrency Mode for its use case. With 𝗥𝘅𝑓𝑥, its easily named and tested modes (which use RxJS operators underneath) allow you to keep your code readable, and you can eliminate race conditions in a 1-line code diff.
The modes, pictorially represented here with use cases and descriptions, are utilized just by calling createService
, createQueueingService
, createSwitchingService
, or createBlockingService
accordingly. Your effect stays the same, only the concurrency is different.
Choose your mode by answering this question:
If the effect is running, and a new request arrives, should the service:
createService
)createQueueingService
)createBlockingService
)createSwitchingService
)And one final mode, seldom used, but included for completion:
createTogglingService
)Here are representations of each mode:
Download SVG
For more information about what went into 𝗥𝘅𝑓𝑥, the following are great reads.
With concurrency, cancelation, animation, user feedback, and other best UX practices. CodeSandbox
Here we build an Alarm Clock (of a variety you may already know!) . Pushing a time/set button down is the request, and the responses are all the updates of hour or minute we get from the H or M keypress events.
Because 𝗥𝘅𝑓𝑥 ensures your services don't depend upon your view, you can port the same service to any UI framework, Web or Native, trivially. These ports of the Alarm Clock to major UI frameworks took under half an hour each to do: