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

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:

<>Copy
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:

<>Copy
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:

<>Copy
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:

<>Copy
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

Share

About the author

author_image
author_image

About the author

Michael Lorton

About the author

author_image
Looking for a JS job?
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
AngularpostTracking user interaction area

13 September 2021

8 min read

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
Angularpost
7 September 202122 min read
Designing Angular architecture - Container-Presentation pattern

Designing architecture could be tricky, especially in the agile world, where requirement changes are frequent. So your design has to support that and provides extendibility without the need for serious modification. In such cases, you will find the Container-Presentation pattern instrumental.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more
micro frontendspostTaking micro-frontends to the next level

6 September 2021

25 min read

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more