0

I've started using reactive extensions (RxJS) and I understand how powerful using Observables can be when the data is spread out in time (or a stream of data). Ex - observing mouseDown events, etc. But I'm struggling to understand their use case in the context of API Calls

I have a server that returns me 5 names when I make an API call. It is a regular GET request which returns all the 5 names in one response. Why should I use an observable over a Promise for this? I don't quite understand using Observables for API Calls.

Help appreciated, thanks.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
marco
  • 1
  • 1
  • What does the first "A" in AJAX stand for? – PM 77-1 Feb 03 '21 at 15:24
  • 1
    Asynchronous, but why not use promises then?. – marco Feb 03 '21 at 15:28
  • @marco there is no reason in the case you name not to use a Promise except (and indeed the builtin Javascript `fetch` function does).... if you model the problem that you are solving as merging and transforming streams of data e.g. by using Observables, then the result is just another stream, albeit one that only emits a single value. If you're doing anything with websockets though you can't really use Promises. – Jared Smith Feb 03 '21 at 15:32
  • 2
    You may want to read this answer: https://stackoverflow.com/a/40135509/2055998 – PM 77-1 Feb 03 '21 at 15:33
  • Observables are a higher level abstraction than Promises. You can conceptually think of Promises as sub-case of an Observable. With this in mind, if you already have an Observable hammer in your toolbelt, you can use it on Promise nails. I don't mean this in a derogative manner, either - if a task can be folded under the same umbrella as other similar tasks, there is not much need to have two solutions for them but one is enough, since it's general. – VLAZ Feb 03 '21 at 15:39
  • cool, got it. thanks for the responses. – marco Feb 03 '21 at 15:53

2 Answers2

1

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 */
}
Mrk Sef
  • 7,557
  • 1
  • 9
  • 21
0

Observables in Angular

This is what the Angular Team says about Observables:

Observables provide support for passing messages between parts of your application. They are used frequently in Angular and are the recommended technique for event handling, asynchronous programming, and handling multiple values.

An observable can deliver multiple values of any type—literals, messages, or events, depending on the context. The API for receiving values is the same whether the values are delivered synchronously or asynchronously. Because setup and teardown logic are both handled by the observable, your application code only needs to worry about subscribing to consume values, and when done, unsubscribing. Whether the stream was keystrokes, an HTTP response, or an interval timer, the interface for listening to values and stopping listening is the same.

Because of these advantages, observables are used extensively within Angular, and are recommended for app development as well.

For more information, see


Observables in JavaScript

In the past there was a push to add Observables to JavaScript. However that effort fell through the cracks when Asychronous Iterators were added to JavaScript.

For more information, see

georgeawg
  • 48,608
  • 13
  • 72
  • 95