Skip to main content

JavaScript Promises — Return Values and Error Handlers

info
This article was originally published in October 2021 at medium.com. Some formatting might have get lost during the migration to this site: If you think you spotted an issue caused by malformed formatting, feel free to open a Pull Request or send me an Email.

A clear rejection is better than a fake Promise.

Handling errors in JavaScript-Promises might seem confusing at first: Not only are there different ways to make an asynchronous Function work by either using the duo infernale async/await or chaining Higher Order functions in the form of then() and passing callback functions for processing a Promise’s outcome — no, you also have to take care of proper exception handling or else cryptic errors will start to bubble up if something goes wrong (and it will, no matter what!).

No one likes the looks of these.

Actually, Promises provide a pretty smart approach to error handling, and it’s easy once you get the hang of it.

If you need to catch up on Promises and async/await, head up to the MDN!

The Loader

This is the class we’re using for our asynchronous showcases. There is nothing fancy going on here — quite the opposite, we’re using a traditional XMLHttpRequest (instead of the en vogue fetch) to demonstrate the process of promisifying an API that does not provide native Promise-support.

The implementation will resolve to the responseText-property of the object available with the response, if, and only if no error occurred — anything else (including an HTTP status other than 200) will reject the Promise.

An error is thrown if the argument passed to load() is not of the type string.

Take your time and go through the source, notice the lines where reject/ throw is used and then continue with the following paragraphs.

Source 1

Successful file loading

The following shows two different approaches to utilize the Promise-interface given with the load()-method. Both examples are doing the same, although a different syntax is used.

async/ await

We are using await to make sure we are properly waiting for the Promise to resolve.

Source 2

then()

We are using then() to chain method calls on Promise-objects. As soon as the Promise is resolved, the function resolve is called. If this looks familiar to you: It is a similar approach comparable to all the wiring we can do when using traditional callbacks.

Source 3

Exception Handling

Let’s have a look at how we can catch errors when using Promises — again, by looking at async/await and then(). Again, both examples fulfill the same purpose.

Using the async-keyword in front of a function magically turns it into an asynchronous function.

Calling load() w/o an argument — async/await

Although the location where the exception is thrown is not part of the Promise we’re explicitly creating in load(), the async keyword makes sure that the method returns a Promise (yes, you’ve read this right. We’ll refer to this later). We’re wrapping the call in a try/catch-block, making sure we’re not letting the error slip.

Source 4

Calling load() w/o an argument — then()

Let this one soak in , one more time: Methods marked as async return a Promise Object, no matter the return value that was originally used for the implementation. This in turn means that then() can be used on the return-value of the async method, and we can conveniently pass a onFulfilled- or onReject-callback to it.

Source 5

… when you say nothing at all

Here’s what happens when we’re neither using async/await nor then(). The exception won’t be caught and bubbles all the way up, no matter the try/catch-block. Why’s that so? It’s because load() spawns an asynchronous process that’ll be doing its work (in this case: won’t) sitting somewhere in the Heap before catch is even aware of itself (I might be exaggerating with this for the dramatic purpose).

“You can’t get away from yourself by moving from one place to another.” (The Promise reflecting the abundance of the async-keyword. Originally a quote from Ernest Hemingway’s “The Sun also rises”.)

Source 6

Treatment of asynchronous return values

Once more: Using the async-keyword in front of a function magically turns this function into an asynchronous function. It does not matter how the function is implemented, the return value will (almost always — see below) be a Promise! Understanding this will make working with Promises much, much easier.

Source 7

In our example, the not-so-familiar-with-Promises developer might assume that the value returned by load() is already the responseText of the XMLHttpRequest. But, this is not how async does it job. General rule of thumb:

If

  • you come across a method tagged with the async-keyword

AND

  • you’re not using await when calling it

then treat the return value like a Promise.

Source 8
Source 9

Rejected Promises

Likewise, an async method not called with the await-keyword will always wrap an exception thrown in a Promise, and this Promise’s state will be set to rejected.

Half the promises people say were never kept, were never made. (Edgar Watson Howe)

Without using await, we’re not able to catch (read: try/catch) an exception that was thrown by the async function. In this case, we need to write an error handler: An onReject-callback, passed to then() as the second argument and chain it to the Promise that may or may not get rejected:

Source 10

An elegant weapon for a more civilized age

If you still feel like you’re more into chaining your Promises by using then(), a more subtle approach towards defining error handlers would be to be using catch() , that behaves just like then() with an onReject -callback defined:

Source 11

The first catch() (or then() using an onReject-callback as its second argument) makes sure that errors are handled and not bubble up. Just like you would process resolved Promises with your onFulfilled-callbacks, a Promise chain would branch into the error-handlers as soon as the first Promise gets rejected.

Source 12

A Promise chain makes sure that the very first catch() or onReject-callback found is used for the most recent error being thrown. Just because we define an error-handler at the very bottom of the chain does not mean we’re creating a direct association exclusively to the Promise object we’re expecting from this call. More exactly, this behavior is the same for onFulfilled -callbacks.

Source 13

Closing Notes

Similar to the onerror-handler defined with the GlobalEventHandlers mixin, there is the onunhandledrejection-handler that will take care of unhandled Promise rejections. It is part of the WindowEventHandlers mixin, and you can read more about it at MDN; here’s an article by Peter Mikitsh, showcasing its usage.