A clear rejection is better than a fake Promise.
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!).
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!
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.
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.
We are using await to make sure we are properly waiting for the Promise to resolve.
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.
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.
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.
load() w/o an argument —
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.
… 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”.)
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.
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:
- you come across a method tagged with the async-keyword
- you’re not using await when calling it
then treat the return value like a Promise.
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:
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:
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.
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.
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.