throwError is not throw error

Post Editor

The American poet Edward Estlin Cummings was famous for his eccentric use of spacing and capitalization, to the point that his name is usually styled as e e cummings. I wonder what he would think of an RxJS question that a friend asked me: “Is returning throwError the same as writing ‘throw error’?”

3 min read
post

throwError is not throw error

The American poet Edward Estlin Cummings was famous for his eccentric use of spacing and capitalization, to the point that his name is usually styled as e e cummings. I wonder what he would think of an RxJS question that a friend asked me: “Is returning throwError the same as writing ‘throw error’?”

post
post
3 min read
3 min read

The American poet Edward Estlin Cummings (1894–1962) was famous for his eccentric use of spacing and capitalization, to the point that his name is usually styled as e e cummings. I wonder what he would think of an RxJS question that a friend asked me: “Is returning throwError the same as writing ‘throw error’?”

The short answer (whatever e e would say) is no. They are completely different, but the fact that my friend asked is interesting.

throwError() is a function; specifically, it’s a function that returns an Observable that immediately errs out.

Let’s say you are writing a function that takes an ID and looks up that ID in a remote database, something like this:

function getItem(id: string): Observable<Item> {
  return http.get(`https://database.xyz/items?id=${id}`);
}

After a while, you notice that many times, the function is being called with an empty ID. The backend eventually rejects the request, but you have to wait for that happen. There are a lot of things you could do, but the best is probably

With this change, the interface to the function remains unchanged: it always returns an Observable, and the request fails for whatever reason, the Observable errs out. All that changes is that with an invalid ID, it errs out instantly without burdening the server. A use like this is the intended purpose of throwError.

Throwing an error is, of course, completely different. It’s not even a function; it’s a statement that interrupts the flow of control. If you had re-written getItem() by having it throw an error, like this:

function getItem(id: string): Observable<Item> {
  if (!id) {
    throw new Error("invalid ID");
  }
  return http.get(`https://database.xyz/items?id=${id}`);
}

That looks almost the same, but it’s just a terrible idea. It actually changes the API: you (or somebody) needs to find every place that calls the function to catch and possibly refactor the code to handle the new exception — and of course, you also still need all the old error-handling code for cases where the network call is tried, but fails.

Why was my friend curious about whether these two very different things were the same? He had seen several instances where the two mechanisms had been used interchangeably. Imagine that instead of a getItem() function, you needed to change a stream of IDs into a stream of Items. This way of writing the code:

const items = ids.pipe(
  concatMap(id => {
    if (!id) {
      return throwError("invalid ID");
    }
    return http.get(`https://database.xyz/items?id=${id}`);
  }),
);

behaves exactly the same as:

const items = ids.pipe(
  concatMap(id => {
    if (!id) {
      throw new Error("invalid ID");
    }
    return http.get(`https://database.xyz/items?id=${id}`);
  }),
);

Seeing that equivalence, my friend understandably wondered if the two forms were always identical. No, they aren’t. Consider the following two expressions:

The type of the first expression is Observable<number>, which makes perfect sense: it’s a stream of numbers that might or might not err out.

The type of the second expression is Observable<Observable<unknown> |number>, a construct about which the kindest thing I can say is that it probably won’t do what you want.

So why did it work in the earlier case? Because of the concatMap()! It looks irrelevant, but remember: concatMap subscribes to the value returned from its function. Since that was the doomed Observable returned from throwError(), it failed just as if an error or exception had been thrown from the function.

Those subscriptions don’t always happen though. In fact, they only happen in flattening operators:concatMap(), mergeMap(), switchMap(), and exhaustMap(). All the regular operators, like map(), scan(), and filter(), do not do this. They just pass on the Observable to the next function. Consider the following:

That error in the first map? It’s just discarded — or rather, it’s transformed into a 1, without ever being unwrapped into a real exception.

Is there any reason to use throwError() inside a pipe? As a function, it can be used in an expression, so there’s an aesthetic argument. Consider this:

It is, arguably, more readable than a statement-based equivalent. You have to compare that advantage against the risk of luring another programmer of trying the same trick somewhere it won’t work, like in a map().

Overall, I would advise against ever using throwError() inside pipes, even in cases where it would technically work. It just confuses people. Use it for what it was designed for: creating Observables that immediately fail.

spring summer autumn winter
he sang his didn’t he danced his did.
Town, e e cummings, 1923
Discuss with community

Share

About the author

author_image
author_image

About the author

Michael Lorton

About the author

author_image
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

Angularpost
4 March 20218 min read
Angular Universal: real app problems

Angular Universal is an open-source project that extends the functionality of @angular/platform-server. The project makes server-side rendering possible in Angular. This article will discuss the issues and possible solutions we encountered while developing a real application with Angular Universal.

Angularpost
4 March 20218 min read
Angular Universal: real app problems

Angular Universal is an open-source project that extends the functionality of @angular/platform-server. The project makes server-side rendering possible in Angular. This article will discuss the issues and possible solutions we encountered while developing a real application with Angular Universal.

Read more
AngularpostAngular Universal: real app problems

4 March 2021

8 min read

Angular Universal is an open-source project that extends the functionality of @angular/platform-server. The project makes server-side rendering possible in Angular. This article will discuss the issues and possible solutions we encountered while developing a real application with Angular Universal.

Read more
Angularpost
3 March 20215 min read
View State Selector  - Angular design pattern

As a web developer you may have noticed a repetitive boiler plate code of displaying a loader while an asynchronous request is being processed, then switching to the main view or displaying an error. Personally, I noticed these repetitions both in my code and other developers I work with. And even worse than the repetitive code is the fact that there are no indications for missing state views (such as unhandled errors or a missing loader). <div *ngIf="data$ | async as data"> <ng-container *ng

Angularpost
3 March 20215 min read
View State Selector  - Angular design pattern

As a web developer you may have noticed a repetitive boiler plate code of displaying a loader while an asynchronous request is being processed, then switching to the main view or displaying an error. Personally, I noticed these repetitions both in my code and other developers I work with. And even worse than the repetitive code is the fact that there are no indications for missing state views (such as unhandled errors or a missing loader). <div *ngIf="data$ | async as data"> <ng-container *ng

Read more
AngularpostView State Selector  - Angular design pattern

3 March 2021

5 min read

As a web developer you may have noticed a repetitive boiler plate code of displaying a loader while an asynchronous request is being processed, then switching to the main view or displaying an error. Personally, I noticed these repetitions both in my code and other developers I work with. And even worse than the repetitive code is the fact that there are no indications for missing state views (such as unhandled errors or a missing loader). <div *ngIf="data$ | async as data"> <ng-container *ng

Read more
RxJSpost
26 February 20213 min read
RxJS: Why memory leaks occur when using a Subject

It's not uncommon to see the words 'unsubscribe', 'memory leaks', 'subject' in the same phrase when reading upon RxJS-related materials. In this article, we're going to tackle this fact and by the end of it you should gain a better insight as to why memory leaks occur.

RxJSpost
26 February 20213 min read
RxJS: Why memory leaks occur when using a Subject

It's not uncommon to see the words 'unsubscribe', 'memory leaks', 'subject' in the same phrase when reading upon RxJS-related materials. In this article, we're going to tackle this fact and by the end of it you should gain a better insight as to why memory leaks occur.

Read more
RxJSpostRxJS: Why memory leaks occur when using a Subject

26 February 2021

3 min read

It's not uncommon to see the words 'unsubscribe', 'memory leaks', 'subject' in the same phrase when reading upon RxJS-related materials. In this article, we're going to tackle this fact and by the end of it you should gain a better insight as to why memory leaks occur.

Read more