Observables are a Superset of Promises.
If you can do it with a Promise
, then you can do it with an Observable
. One reason you might stick with Promises
is that Observable
don't have built-in syntactic sugar in JavaScript (async await).
Mixing the two isn't advisable. There's no performance or interoperability issues, but if you're going to be using RxJS Observables anyway, then it's clearest (maintainability, extend-ability, debug-ability) to just stick with Observables as much as possible.
Why Use Observables?
Programs often perform tasks over time. You can abstract away a level of callbacks by treating data as a stream over time.
Instead of
async function userButtonPress(event){
const response = await apiCall(event.thingy);
/* More code */
}
you get
userButtonPresses$.pipe(
mergeMap(event => apiCall(event.thingy))
).subscribe(response => {
/* More code */
});
Is this better? Well, yes, it means you're working with button presses as a stream of data instead of as a callback function. The benefits are varied.
Every button click starts another concurrent api call
This is what an api call inside an event callback acts like if it's not somehow managed.
userButtonPresses$.pipe(
mergeMap(event => apiCall(event.thingy))
).subscribe(response => {
/* More code */
});
Every button click queues another api call that doesn't start until the previous one completes
If you want a Promise
to wait its turn, you'll need a library or data-structure that lets your function know when it's free to start.
userButtonPresses$.pipe(
concatMap(event => apiCall(event.thingy))
).subscribe(response => {
/* More code */
});
Every button click cancels the ongoing api call (if there is one) and starts a new one
Promises
don't have a native way to cancel in-flight processes, so you'll need a library that extends promises with some extra functionality. This is on top of the one you need to manage concurrent promises.
userButtonPresses$.pipe(
switchMap(event => apiCall(event.thingy))
).subscribe(response => {
/* More code */
});
How many button presses have there been so far?
userButtonPresses$.pipe(
switchMap((event, index) => apiCall(event.thingy).pipe(
map(response => ({response, index}))
))
).subscribe(({response, index}) => {
console.log(`This is response #${index}: `, response);
/* More code */
});
Ignore button presses if they're closer than one second together
With promises you can set a variable (scoped outside your callback function) with const time = Date.now()
and see if 1 second has passed before starting your api call. With RxJS it's a simple operator.
We'll still count button presses that we ignore, but we'll ignore button presses that come too closely together.
userButtonPresses$.pipe(
map((event, index) => ({event, index})),
throttleTime(1000),
switchMap(({event, index}) => apiCall(event.thingy).pipe(
map(response => ({response, index}))
))
).subscribe(({response, index}) => {
console.log(`This is response #${index}: `, response);
/* More code */
});
A button press starts polling an API endpoint every 500ms
We'll still count button presses, and throttle them too (because why not?).
userButtonPresses$.pipe(
map((event, index) => ({event, index})),
throttleTime(1000),
switchMap(({event, index}) => timer(0, 500).pipe(
concatMap(_ => apiCall(event.thingy).pipe(
map(response => ({response, index}))
))
)
).subscribe(({response, index}) => {
console.log(`This is response #${index}: `, response);
/* More code */
});
Example With Promises
Here, you're going to need variables to handle everything manually. It's harder to test and there's no guarantee some other process isn't messing with those variables. Even something simple like cancelling a previous api call if the next button click comes before the call is finished doesn't work with native promises.
Here's how you might count button presses
let buttonPresses = 0;
async function userButtonPress(event){
// There's no saying how often the global buttonPresses will be incremended while
// we await this promise, so we need a local copy that doesn't change.
const inScopeButtonPresses = buttonPresses++;
const response = await apiCall(event.thingy);
console.log(`This is response #${inScopeButtonPresses}: `, response);
/* More code */
}