Understanding Asynchronous Requests and Observables

If you’ve done any JavaScript programming, you are familiar with callbacks. These are functions that get called after some work has completed. The simplest example is setTimeout, which takes a callback function and a number of milliseconds. After the given milliseconds have elapsed, the function is called.

In this code, the function errorMessage is executed one second later:

 var​ greeting = ​function​() {
  alert(​"Hello!"​);
 };
 setTimeout(greeting,1000);

You first saw this when using Http in Chapter 6, Build a Dynamic UI with Angular, when you passed a callback to subscribe that would execute our code once we got a response from the server. I didn’t really talk about why Http works that way. Let’s do that now.

Why Asynchronous?

Contacting the server takes time. Even with all the improvements we’ve made in the performance of our Rails controller, fetching data over the Internet is not instantaneous. If our JavaScript were to make an Ajax call and wait for a response (called a synchronous request), the entire browser would be hung while it waited on the network.

To prevent this, you want to make the call in the background and let your main bits of code (and the browser in general) continue to operate while the networking request is happening. In many programming languages, this is done with threads. JavaScript does not expose the concept of threads to the user, instead requiring programmers to use callbacks managed internally by the runtime.

This means your code is often organized into three chunks: (1) the setup code to make our Ajax request, (2) the code to actually make the request, and (3) the code to run after the request has completed. Where this can get tricky is when you have more than one request. Consider this code:

1: var​ base = ​"http://billing.example.com"
2: self.http.get(base + ​"/cardholder/123"​).subscribe(​function​(response) {
3:  alert(​"Got card details!"​);
4: });
5: self.http.get(​"/customer/123.json"​).subscribe(​function​(response) {
6:  alert(​"Got customer!"​);
7: });
8: alert(​"Requests sent!"​);

Although this code looks like it runs top to bottom, it actually doesn’t. Lines 1 and 2 execute first. While that Ajax call is happening, the code proceeds to line 5. After that, line 8 is called.

This means that lines 3 and 6 haven’t executed yet! They’ll execute when their respective Ajax calls complete, and that could happen in any order. A big part of our code—handling the responses from the server—cannot rely on any particular ordering. Following is a diagram of the overall flow.

images/callback_flow.png

This can get confusing fast, especially if the code does need to run in a certain order. Consider if we needed a credit card holder ID from our customer details in order to fetch the customer’s card information from our third-party billing service. We’d need to make our call to the billing service inside the callback that’s called when our customer details are fetched. This creates a nested structure like so:

 var​ base = ​"http://billing.example.com"
 self.http.get(​"/customer/123.json"​).subscribe(​function​(response) {
  alert(​"Got customer!"​);
  self.customer = data.json().customer;
 var​ url = base + ​"/cardholder/"​ + self.customer.cardholder_id;
  self.http.get(url).subscribe(​function​(response) {
  alert(​"Got card details!"​);
  });
 });
 alert(​"Requests sent!"​);

This is called callback hell. It makes your client-side code hard to deal with. Partly this is just the reality of programming with an asynchronous model, but there are tools like promises and observables to help tame it. Angular uses observables.

Observables

We looked at observables a bit in previous chapters, and what we know about them is that instead of a passing a callback to http.get, we pass a callback to the object that http.get returns. As mentioned in Get Data from the Back End, http.get returns an RxJS observable, and subscribe is a method on that observable that allows us to execute code when…well, when?

As you have come to expect by now, Angular is built on abstractions of abstractions. If you read the documentation[74] for the subscribe method of an RxJS observable, it says

Subscribes an observer to the observable sequence.

This is not a specific explanation, and it sure doesn’t sound like it has anything to do with making Ajax requests. This is because observables are a generic construct that Angular uses to implement its Ajax-handling code. If you look at the main documentation for an observable,[75] you’ll see many, many methods. They might look similar to methods on Ruby’s Enumerable or any other programming language’s collections library.

That’s because observables are meant to allow asynchronous operations on streams of data, which is called an observable sequence. This may be somewhat confusing, because if you consult the documentation for Http,[76] you’ll see that we’re only observing a sequence of one item—the entire response:

Calling [get] returns an Observable which will emit a single Response when a response is received.

Angular is treating a response from our Ajax request as a special case of an observable sequence. That allows it to reuse a more generic library, rather than roll its own custom mechanism (which is what both Angular 1 and jQuery do). It also means that we can make use of that generic libraries features to more cleanly structure our code.

Now that we now we are using observables, we can reexamine the previous code and structure it differently. The code needs to transform the response into the JSON object we want, and then do two things: (1) store the result in a property, and (2) kick off another Ajax request. In the language of an observable, we want to map the response to something else, and then subscribe two bits of code to that response.

You’ve seen the latter used for a single bit of code, and for the former, the map function allows you to encapsulate the transformation code in one place. Suppose you have the following functions (note how small and single-purpose they are):

 var​ parseCustomer = ​function​(response) {
 return​ response.json().customer;
 };
 
 var​ setCustomer = ​function​(customer) {
  self.customer = customer;
 };
 
 var​ setCreditCardInfo = ​function​(response) {
  self.credit_card_info = response.json();
 }
 
 var​ requestCardInfo = ​function​(customer) {
 var​ url = ​"http://billing.example.com"​ +
 "/cardholder/"​ +
  customer.cardholder_id;
  self.http.get(url).subscribe(​function​(response) {
  self.credit_card_info = response.json();
  });
 }

You can use them with the observables that come back from http.get to keep the Ajax-requesting code clean and straightforward, like this:

 var​ self = ​this​;
 var​ observable = self.http.get(​"/customer/123.json"​);
 
 var​ mappedObservable = observable.map(parseCustomer);
 
 mappedObservable.subscribe(setCustomer);
 mappedObservable.subscribe(requestCardInfo);

To do this, start with an observable that makes the Ajax request and returns the raw JSON response when it’s complete. Next, use map to create a new observable (mappedObservable) that notifies subscribers of the result of parseCustomer instead of the raw response. Then subscribe to that observable so that our two chunks of code can be given a nicely formatted customer instead of raw JSON. The following diagram shows what that setup looks like visually.

images/ObservableSetup.png

Once you get a response back from the server, observable is notified, which calls parseCustomer and then notifies the two subscribers of mappedObservable as shown in the next diagram. We’ll add code like this to Shine in a moment.

images/ObservableResponse.png

As you can see, observables are a deep topic, and we’ve learned most of what we need in order to do our jobs, but a detour through the documentation or Reactive Programming with RxJS [Man15] will reveal a powerful set of tools to manage your application as it grows in complexity.

Now that you know a bit more about the asynchronous nature of HTTP requests and how Angular manages them, let’s get back to adding the credit card information to our customer detail view. The first step to doing that is to break up the existing component into several subcomponents.