- 24 minutes read

Asynchronous programming is hard. Evolution gifted us with a marvelous brain capable of doing many things, but asynchronous and concurrent processes weren't top on the list. We can do it, but only to a certain degree. You should do everything to keep it simple. The async-await pattern is a marvelous tool to reduce complexity.

But what does Angular do? Several years ago, Angular replaced the Promises of AngularJS 1.x with Observables. Since then, Promises are frowned upon. async/await has been invented later, but from an Angular programmers point of view, async/await never became a thing. That's a pity, because it simplifies your REST calls tremendously.

By the way, during my research, I learned that Netanal Basal had the same idea five years ago. Maybe I'm not entirely wrong. Another hint is that Aurelia - the framework that tries to be the better fork of Angular - doesn't use Observables for simple HTTP requests, either. Aurelia still uses Promises.

TL;DR

This is a long article (once again!), so let's summarize it quickly. In a nutshell, an Observable is a Promise with multiple values. But 90% of Observables in an Angular application deal with simple HTTP calls. 90% of your Observables never return multiple values. It's only a single value. The power of Observables is wasted. Simple Promises are the better tool - especially since the invention of async / await. The RxJS team has invented the method firstValueFrom() or - until RxJS 6 - toPromise() for a reason.

async / await converts your callback hell into simple, linear code. You don't have to hassle with nested Observables, mergeMap(), forkJoin(), switchMap(), and mergeMap(). Instead, you'll write almost the same code your Spring backend uses. One line is executed after the other, without nasty surprises. But - that's the different to Spring code - it's still non-blocking I/O. The best of two worlds!

When I suggest that, I usually hear that you'll loose a lot. You can cancel a Promise, nor can you set a timeout, let alone retry the call when it fails, and you'll generate a memory leak because the Observables are not unsubscribed. That's true, and at the same time it's nonsense. I don't want you to throw RxJS into the dustbin. Quite the contrary. When you're converting a httpClient.get() REST call into a Promise, it's still an Observable at heart. Here's how to have the cake and to eat it, too:

const countries = await httpService.get("https://restcountries.com/v2/all") .pipe( takeUntil(this.componentIsDestroyed$), takeUntil(this.cancelRestCall$), timeout(30000), retry(3) ).toPromise();

Once you've got there, all the other advantages of Observables stop being advantages. I'll show that most operators of RxJS make your code more convoluted instead of making it more manageable.

Does my example look verbose? Yeah, but I guess that's because you never cancel your REST calls, nor do you add a timeout or retry them. FWIW, I've hardly ever seen any of them in the wild. That's another fun fact: when developers tell you they prefer Observables because of these features, ask them if they are using them. You'll be surprised. Nonetheless, it's a good idea to use them, and it's easy to define a function covering the long pipe and the toPromise() or firstValueFrom() call.

Don't get me wrong. I love RxJS. An Observable is just great when you're dealing with a stream of events. All I'm saying is: let's choose the right tool for the job. If your Observable is only a glorified Promise, convert it to a Promise.

What's the thing about async/await?

First of all, as much as I love the async / await pattern, it's not a silver bullet. Writing asynchronous code is hard, and it remains hard. You can't use async / await without knowing what a Promise is. It took me some time to wrap my head around Promises. And I still haven't understood it perfectly. For example, how to iterate a folder using the asynchronous version of fs.readdir()? Granted - that's easy, but only until you want to do it recursively. Implementing asynchronous code with callbacks recursively is a skill you have to learn.

Let's have a look at the recursive folder task. It's not an Angular example, but it shows how challenging callback functions are.

Here we go. I did what every good programmer does: I googled and found an answer at StackOverflow. Here's how the StackOverflow experts list a directory recursively:

const fs = require('fs'); const path = require('path'); function walk(dir, done) { let results = []; fs.readdir(dir, function(err, list) { if (err) return done(err); var pending = list.length; if (!pending) return done(null, results); list.forEach(function(file) { file = path.resolve(dir, file); fs.stat(file, function(err, stat) { if (stat && stat.isDirectory()) { walk(file, function(err, res) { results = results.concat(res); if (!--pending) done(null, results); }); } else { results.push(file); if (!--pending) done(null, results); } }); }); }); };

Honestly: I couldn't write that. Or it'd take me forever. It's not enough to call the walk() function recursively. You also need the result array and the pending counter. Both are crucial to make this algorithm work. I guess it's merely a pattern, a skill I can learn - but it's a skill I don't want to learn. More to the point: I don't want to force my co-workers to learn this skill. There must be an easier way!

Let's have a look at the same algorithm using async / await:

const { resolve } = require('path'); const { readdir } = require('fs').promises; async function getFiles(dir) { const dirents = await readdir(dir, { withFileTypes: true }); const files = await Promise.all(dirents.map((dirent) => { const res = resolve(dir, dirent.name); return dirent.isDirectory() ? getFiles(res) : res; })); return files.flat(); }

Wow. Eight lines instead of 22 lines. Plus, it's easy as pie. It almost looks like its synchronous counterpart.

That's the beauty of async / await. It's still asynchronous code. But it reads and writes like synchronous code. Usually, it's also shorter.

Observables are more powerful than Promises

At this point the average Angular architect usually says "Yes, but...". They list a long list of "buts", of things Observables do better than Promises. They most popular "but" is:

Observables can deal with streams of events. Promises can only deal with one single event.”

Yeah. Dealing with streams of events is pretty cool when you need it. But you hardly ever do.

Have a look at your application. How many of your Observables return more than one result?

I bet the vast majority of your Observables are simple REST calls. That's one result from the server. One. At most. It's either a success or an error message. REST calls don't deal with streams of events. 90% of your application doesn't. You need Observables only for the remaining 10%.

In other words, the majority of RxJS operators is useless to you. You'll never filter() a stream, nor will you skip() some of the messages of the stream, nor will you zip() to streams of events.

Single events can indeed be considered a special - just very short - stream of events. When the Angular team decided to drop Promises in favor of RxJS, they had a point. Both APIs use callback functions. Both APIs have similar goals. So it makes sense to use a uniform API. Or at least, it made sense at the time because async / await had yet to be invented. As far as I remember, async / await made it to the JavaScript world one or two years later.

The dirty secret is async / await is a game-changer. It's the tool of choice for REST calls.

Cancelling Observables

Only it isn't, they say. You can't cancel a Promise. Embracing Promises you can never cancel your REST calls.

Before answering that, let's have a look at why you might want to cancel a REST call.

Imagine your user opens a page of your application and leaves it before the server responds to the request. In this case, you'll want to cancel your REST call. You're not interested in the response. That's easy with the httpClient.get(). It cancels the Observable automatically.[1] However, there's no way to cancel a Promise. When the server sends its response, the callback function of your Promise is executed. That's dangerous. Chances are the garbage collector has already removed the data structures your Promise callback needs. So you end up with mysterious error messages. Even if your application doesn't run into errors, it still executes code that's no longer needed. That's a waste of resources.

But is this true?

No, it's not. Not with the approach I've got in mind. I don't suggest to throw Angular's httpClient into the dustbin. Instead, I want to wrap calls like httpClient.get() in a Promise façade like so:

import { firstValueFrom } from 'rxjs'; ... const url = 'https://example.com/bookshelf'; const bookshelf: Array = await firstValueFrom(httpClient.get(url); console.log(Found ${bookshelf.length) books in my bookshelf);

If you're still using RxJS 6, you need to use toPromise() instead of firstValueFrom():

const bookshelf: Array = await httpClient.get(url).toPromise();

Both toPromise() and firstValueFrom() support cancellation. They are just shallow wrappers around the Observable. Being the curious guy I am, I've read the RxJS source code, just to be sure. firstValueFrom() throws an EmptyError when the underlying Observable is cancelled. As long as you catch your exceptions you can deal gracefully with the ngOnDestroy challenge. If the user leaves the page before the server responds, the request is cancelled, and your catch block can deal with it.

Problem solved!

Cancelling http request programatically

Well... not quite. It always pays to listen to Pascal Precht. Imagine you were to implement the Google search box. Every time the user hits a key, a search is triggered. Many of us type faster than our backend responds. So you trigger many server requests, but you're only interested in the last response. You'll want to cancel the other REST calls. Adding insult to injury, you must cancel the outdated REST calls because the server may respond out-of-order.

There's that. It's an exotic use-case, but still, it's a valid use-case. It's also a use case you should avoid at all costs. When you cancel a REST call, that only means you cancel it on the client-side. The server still processes the REST call. If you're worried about climate change (or your AWS bill or server-side performance), avoid that. Add a debounce() to the Observable triggering the REST call.

Come on, that's an interesting (if slightly exotic) scenario. Among other things, it shows that Observable can peacefully - even fruitfully! - co-exist with async / await. I've modified an example I've found at www.freakyjolly.com:

@Component({... }) export class AppComponent implements OnInit { @ViewChild('movieSearchInput', { static: true }) public movieSearchInput: ElementRef; private apiResponse: Array; constructor(private httpClient: HttpClient) { } public async ngOnInit() { fromEvent(this.movieSearchInput.nativeElement, 'keyup') .pipe( map((event: KeyEvent) => event.target.value), filter(res => res.length > 2), debounceTime(1000) distinctUntilChanged(), tap(text => this.apiResponse = await this.searchGetCall(text)) ).subscribe(); } private async searchGetCall(term: string): Promise { if (!term) { return of([]); } const url = http://www.omdbapi.com/?s=${term}&apikey=${APIKEY}; const params = { params: PARAMS.set('search', term) }; return firstValueOf(this.httpClient.get(url, params)); } }

This search box goes to great lengths to prevent the scenario Pascal Precht describes. Your search keyword requires at least three letters, there's a long debounce time of one second - which means that there's at least one second between consecutive server calls, and even more, if you hit a key twice a second - and it even adds the nice distinctUntilChanged() operator.

RxJS rocks!

The async / await block comes next. It only deals with the REST call. That's the call you want to cancel. Maybe you've set the debounce time lower, or your network is extraordinarily slow, so OMDBAPI takes more than one second to respond. Can we cancel the request programmatically when the user hits the next key?

Yes, we can!

Actually, that's merely pimping up the REST call:

return firstValueOf(this.httpClient.get(url, params).pipe( takeUntil(this.cancelPreviousRESTCalls$) ));

Oops, that was probably a bit fast. Let's walk you through it step-by-step.

Actually, it's easy. You can cancel any Observable using takeUntil(). It doesn't make any difference your've wrapped the Observable into a Promise. You can still use takeUntil(). We've already seen that a minute ago: When the Observable is cancelled, an EmptyError is risen.

This is what the complete solution looks like. Take it with a grain of salt. It's pseudo-code, possible including syntax errors, but the idea is still valid:

@Component({... }) export class AppComponent implements OnInit { @ViewChild('movieSearchInput', { static: true }) public movieSearchInput: ElementRef; private apiResponse: Array; private cancelPreviousRESTCalls$ = new Subject(); constructor(private httpClient: HttpClient) { } public async ngOnInit() { fromEvent(this.movieSearchInput.nativeElement, 'keyup') .pipe( map((event: KeyEvent) => event.target.value), filter(res => res.length > 2), debounceTime(1000) distinctUntilChanged(), tap(() => this.cancelPreviousRESTCalls$.emit()), tap(text => this.apiResponse = await this.searchGetCall(text)), catch(exception => { if (!(exception instanceof EmptyError)) { console.error("An error has occurred: ", exception); } }) ).subscribe(); } private async searchGetCall(term: string): Promise { if (!term) { return of([]); } const url = http://www.omdbapi.com/?s=${term}&apikey=${APIKEY}; const params = { params: PARAMS.set('search', term) }; return firstValueOf( this.httpClient.get(url, params).pipe( takeUntil(this.cancelPreviousRESTCalls$) )); } }

In a nutshell, all you need to do is implement cancelPreviousRESTCalls$ and catch the exception. You end up with a version that's as lengthy as the pure RxJS solution. Sorry about that. On the plus side, you've got one callback less. One thing less to wrap your head around.

Maybe that's not the simplification I've promised you. But that's OK. We've examined an exotic use case. I still think using async / await is a good choice, even in this case, but I also think it's also OK to use pure RxJS. It's your choice.

The other point I'd like to bring home is that canceling a REST call is evil. You can't stop the server from processing the event. That's a shortcoming of the HTTP protocol, but at the time being, that's just the way it is.

Memory leaks

Time for the next "but." What happens if the server never answers?

If you're using an Observable, that's not a big deal. You're collecting every Subscription and unsubscribe them in the ngOnDestroy method. At least I hope so. I've covered this in detail some time ago.

That's something you can't do with a Promise. Or is it?

Well, first, I don't believe you unsubscribe your REST calls. Because HttpClient always calls complete when the server response arrives. That's something you can rely on. The only exception is when the server never sends a response. So common wisdom is not to unsubscribe calls initiated by HttpClient. You can unsubscribe from the subscriptions, but that's only for the paranoid. More often than you, you'd end up unsubscribing from a subscription twice.

Plus, it's not true you can't unsubscribe a promisified httpClient.get(). There's an RxJS operator for that. You've already seen it: takeUntil() is your friend. I also recommend adding a timeout. That's adding to the code I want to simplify - but hey, I didn't promise you a rose garden. Now the code looks like so:

const bookshelf: Array = await firstValueFrom(httpClient.get('https://example.com/bookshelf') .pipe( takeUntil(this.cancelPreviousRESTCalls$), timeout(30_000) ));

I've shown most of the takeUntil() approach above. Now I recommend you also call cancelPreviousRESTCalls$.emit() in the ngOnDestroy() method.

Retrying http calls

This is another popular "but" I've hardly ever seen in the wild. Supporters of RxJS insist that RxJS makes it easy to implement a retry mechanism. If the server fails to answer immediately, try several times again. Maybe the next request succeeds. Using RxJS, that's a walk in the park:

http.get(url) .retry(3) .subscribe(...);

Another, more sophisticated example looks like so:

http.get(url) .retryWhen(attempts => { return Observable .range(1, 3).zip(attempts, (i) => i) .flatMap(i => Observable.timer(i * 1000)); });

Or, just another example. How do you like this? Isn't it easy?

this.http.get('./country-info/' + country + '.json') .retryWhen(error$ => error$.switchMap(err => navigator.onLine ? timer(1000) : fromEvent(document, 'online')) // ONE LINE ADDED .map((res: Response) => res.json()) .subscribe(res => this.capitol = res.capitol);

There's no doubt about that: all these examples rock.

And none of them is saying that toPromise() or firstValueFrom() are bad. What about this solution?

const bookshelf: Array = await firstValueFrom(httpClient.get('https://example.com/bookshelf') .pipe( rety(3) ));

It's just that easy!

Promises trap errors

Here's another popular point against using Promises. They say Promise swallow errors.

So what? A close look reveals that the complete statement is "Promises swallow errors unless followed by a catch block."

Using async / await, that's always true. await converts both exceptions and rejected promises to an exception. Actually, using async / await is a good way to make sure errors are never silently trapped.

Promises are always asynchronous while Observables are synchronous

I love this "but". Because it's sort of true. There's a fundamental difference between Promises and Observables. An Observable is executed synchronously. A Promise is not. The callback method of a Promise is always added to the microtask queue.

Isn't that a disadvantage? If we're using Promises, do we lose control over our application? Do we end up with a plethora of threads?

Not quite. Many - even experienced - developers talk about threads in JavaScript. But fear not: There's no such thing in JavaScript.[2] JavaScript works exactly the way you do in real life. There's a pile of to-dos on your desktop. Each time your boss looks by, you add a couple of to-dos to the top of the pile. JavaScript does something similar. When you call a Promise, your callback is added to the pile of microtasks.

It's true that Observables don't do that. But in this article, I'm specifically talking about REST calls. httpClient calls are inherently asynchronous. The response always arrives with a delay. It's processed as one of the delayed tasks of the microtasks queue.

So there's almost no difference if you call your REST call as a Promise or an Observable. Plus, my approach is to wrap the Observable in Promise. So there's no difference at all.

Just enjoy the simplified API!

Observables have many operators Promises don't have

Come on. I've already sold it to you, haven't I?

"No, no, no," the RxJS fans say, "you're about to lose all those useful RxJS operators"!

That's a cool "but." It sounds true, and it resonates with me. But still, it's rubbish.

Sorry, guys. RxJS has to offer many powerful operators for a reason. It needs them to pull you out of the callback hell it creates. The RxJS operators solve problems you don't have without RxJS.

Does that sound a bit harsh? Well, first of all, let me remind you I'm only talking about REST calls. Second, I'll prove my point with source code.

Three common scenarios cross my mind:

  • A single REST call.
  • Two consecutive REST calls. The second REST call needs data provided by the first REST call.
  • Two parallel REST calls. You need both responses to proceed.

If it were only for the first scenario, I wouldn't write this article. That's fairly simple, no matter if you're using Observables, classical Promises or contemporary async / await.

Two consecutive REST calls depending on each other

Two consecutive REST calls are nasty with Observables because you're nesting Observables. You need the switchMap() operator to get out of this mess. Being a seasoned Angular developer, you know the drill:

const shelfUrl = 'https://example.com/bookshelf'; const firstBookContent$: Observable = httpClient.get(shelfUrl).pipe( map(books => books[0]), map(firstBook => firstBook.isbn), map(isbn => https://example.com/book/content/${isbn}), map(url => httpClient.get(url)), // now we're dealing with an Observable // watching an Observable switchMap(content => content) // now we're dealing with a simple Observable, // and content is a simple string ).subscribe();

That happens all the time. Nested Observables are bread-and-butter business in the Angular world. Operators like switchMap() and mergeMap() come natural to most Angular developers.

However, await avoids the problem altogether. You'll never deal with nested Promises. await is sort of the equivalent of switchMap(). It's a lot more intuitive and straight-forward. Just look at the source code:

const shelfUrl = 'https://example.com/bookshelf'; const bookshelf: Array = await firstValueFrom(httpClient.get(shelfUrl); const firstBook: Book = bookshelf[0]; const isbn: string = firstBook.isbn; const bookUrl = https://example.com/book/content/${isbn}; const firstBookContent: string = await firstValueFrom(httpClient.get(bookUrl);

As you can see, you're dealing with the base types. There are no callbacks, and you don't have to wrap your head around an abstraction like Observable. Programming is fun again!

Parallel REST calls

Promises makes it particularly easy to call multiple REST calls in parallel. Just use Promise.all(). If you need to support older browsers, there's a polyfill for that. Let's go:

const bookshelfPromise = firstValueFrom(httpClient.get('https://example.com/bookshelf'); const userDataPromise = firstValueFrom(httpClient.get(https://example.com/user/${userName}); const [bookshelf, userData] = await Promise.all([bookshelfPromise, userDataPromise]); // now both bookshelf and userData are available

Of course, there's a RxJS operator covering this usecase:

import {Observable} from 'rxjs/Observable'; Observable.forkJoin( this.http.get('https://example.com/bookshelf'), httpClient.get(https://example.com/user/${userName}) ).subscribe(response => { const [bookshelf, userData] = response; });

In this particular case, the RxJS version doesn't look that convoluted. I still prefer using Promises. await allows me to deal with the real data types.

Chaining RxJS operators

Chaining operators is strangely attractive to developers. That even includes me. It feels good to break the algorithm into small chunks, each in a single line, linked by fancy RxJS operators. The resulting algorithm looks like you went into great length to make it simple. RxJS offers a wide range of powerful operators to make your life simple.

The dirty little secret is that this isn't true. Most RxJS operators solve problems caused by RxJS. Granted, we've already seen there are useful operators, such as retry(), skip(), takeUntil(), and timeout(). By now, you know you can easily replace forkJoin() by Promise.all(), and you never miss switchMap() when you're using async / await. Let me add just one single example. What happens to our beloved map() operator?

Here's the traditional RxJS code:

const url = './country-info/' + country + '.json'; this.http.get(url) .pipe( .map(country => country.capital), .tap(capital => console.log(capital)) ).subscribe();

One map() and a tap(). How cool is that!

const url = './country-info/' + country + '.json'; const res = await this.http.get(url); const capital = res.capital; console.log(capital);

That's a bit anticlimactic, isn't it? The map() has gone away for good. Now it's a simple assignment. The tap() operator is gone, too. Just the body of the closure called by tap() remains.

Working with arrays

Working with arrays is a real pain the ass if you're using Observables. More often than not, the server sends an array of data. Of course, you can deal with the array using RxJS. The thing is, RxJS uses almost the same operators you also need to process arrays. If you love functional programming, that is. So I often end up with two map() statements in the same line. To me, that's not a big deal. I've written the code. I know that the first map() is an RxJS operator and the second map() belongs to arrays.

const carsOrderedByPrice$: Observable = this.httpClient.get('/api/cars').pipe( map(cars => cars.sortBy(car => car.price )), map(orderedCars => orderedCars.map(car => car.name)) );

But every time I try to teach Angular to developers with a Java background, they're confused. Distinguishing between the two map() functions and detecting which map() plays which role is a skill you have to learn. Async / await takes the sting out of it. It simply remove the RxJS map() from the equation. We've already seen that a minute ago: map() is replaced by a simple assignment.

Embracing await converts the same algorithm to a walk in the park:

const cars = await firstValueOf(this.httpClient.get('/api/cars')); const orderedCars = cars.sortBy(car => car.price ); const carsByPrice = orderedCars.map(car => car.name);

Lazy execution

They say lazy execution is a big plus of Observables. I guess that's true. The funny thing is, I can't remember a single time I've needed lazy evaluation. My average use case is that my application needs data from the server, and it can't proceed without this data. Lazy evaluation is exactly the opposite of what I need. I don't want my application to wait before fetching the server data. I need the data now, and there's no point in deferring the REST call.

I wonder when evaluating a REST service lazily is an advantage. Remember, I'm only talking about REST service. Generally speaking, lazy evaluation is a great tool. But this article is specifically about REST services. I suppose you call your REST service when you need them. If so, you'll never execute your observables lazily. Promises do the trick just as well.

A single programming paradigm is better then needing two paradigms

When the Angular team decided to drop Promise in favour of Observable, both approaches were using callback functions. It doesn't matter if you have to wrap your head around Promises or Observable. It's pretty much the same learning curve. At the time, it was a good decision to drop Promises in favor of Observables. When both approaches are equally painful, it makes sense to adopt the broader use-case.

However, async / await is a game changer. And you've already seen that I don't condemn Observables. Quite the contrary, I suggest combing the power of Observable with the simplicity of async / await.

From this point of view, I'm convinced it doesn't make sense to use a single pattern. Let's use async / await for the simple cases because it matches the way our brain works. At the same time, let's enjoy the power of Observables.

What about the built-in fetch API of the browser?

An attractive alternative to using Angular's HttpClient is using the built-in API of the browser. Actually, I encourage you to examine it. But at the time of writing, it's not perfect. Most of the popular arguments against using firstValueOf() are valid if you're using fetch. For instance, you can't cancel a fetch() call.

So, no, don't use the native API of the browser. Not yet, that is. The fetch() API is still evolving. When stuff like timeouts and cancelling lands, using the native API of the browser is going to be the way to go. Until then, stick with Observables and decorate them with a cute little firstValueFrom() or toPromise().

Eat your own dog food

I'm not there yet - but after thinking so long and so hard about it, I've decided to replace the Observables of my blog with async / await. The first few tests were very successful. I'm going to eat my own dog food. If it's a failure, I'm going tell you. But this experiment will work!

Wrapping it up

I'd like to hear from you. There must be a reason why the Angular community sticks with Observable. It's weird that even influencers like Netanal Basal suggest using toPromise(), only to be ignored.

I believe it's just the momentum of embracing RxJS (plus being told that RxJS is superior). But I'm always open to new ideas. If you've got a good reason why using httpClient.get().toPromise() is a bad idea, please drop me an email at articles@beyondjava.net. Just remember, I do love RxJS because of its power. If you believe I'm wrong, and if you want to convince me, I'd appreciate you covering REST calls because that's what this article is about.

In the meantime, I'll enjoy the beauty of pseudo-linear code.


Dig deeper

Stop using observable when you should use a promise by Netanel Basal

Http requests as Promises

JavaScript Theory: Promise vs Observable

Do Observables make sense for http?

toPromise() is being deprecated (in favor of firstValueFrom)

How to implement an interceptor to set a timeout globally

Iterating a folder structure recursivly


  1. At least I'm told it's canceled automatically. I haven't run a test yet. If you want to be sure, unsubscribe the event in the ngOnDestroy() method using one of the approaches I've described at https://www.beyondjava.net/unsubscribing-from-an-observable-in-angular.
  2. except for Service Workers.

Comments