Exception Handling in JavaScript Promises
- pencil15 Nov 2021
- clock6 min read
Introduction to promises
A promise is a special JavaScript object returned by an asynchronous function. At the time it is returned to the caller, generally the operation is not finished. However, the Promise
object provides methods to handle the eventual completion or failure of the operation.
Imagine a function doSomething()
that takes in an input parameter input
and two callback functions, successCallback
and errorCallback
– doSomething(input, successCallback, errorCallback)
. The callback functions are called when the operation is successful and when there is an error respectively.
Using a promise, doSomething()
can be rewritten to return a Promise
object as such:
The then
method is a method of the Promise
object that takes in two optional parameters – two handlers to deal with the success and failure of the promise. The first handler is the resolve handler that handles a resolved promise (when the promise is successful). The second handler is the reject handler that handles a rejected promise (when the promise is not successful).
An advantage of a promise is that it allows you to chain operations together so that they run sequentially, instead of nesting another operation within the successCallback
. This is because the then
method returns a new Promise
, for which the then
method can be called again on this new promise to perform another operation.
In other words, instead of:
you can do:
which is arguably neater and easier to read. Note the catch
method which we will get to in a second.
Exception handling
Exceptions can happen in promises, just like anywhere else in a program. How do we handle exceptions in promises?
There are 2 built-in ways to do so.
1. Reject handler
As mentioned above, a reject handler is a function passed into the then
method to handle rejected promises.
In each link of the chain, exceptions are handled by the reject handler in the subsequent link. Take the promise chain below. An exception in link 1 will be handled by the reject handler in link 2 (rejectHandler1
). An exception in link 2 will then be handled by the reject handler in link 3 (rejectHandler2
), and so on.
Once the exception is handled, the rest of the chain continues.
Note that not each link requires a reject handler since it is optional to pass in a reject handler to the then
method. In the event where the reject handler is not defined, the then
method will use its default reject handler, which rethrows the exception: (err) => {throw err;}
. The exception will then be handled by the reject handler in the subsequent link. In the example below, exceptions in links 1 and 2 are handled by rejectHandler2
, because there is no reject handler defined in link 2.
This means that:
An exception in a chain will be handled by the first reject handler it sees down the chain.
2. catch
method
In a chain with multiple links, the reject handler could be the same for all links. In this case, you can use the catch
method:
The catch
method is a method of the Promise
object. It is effectively the then
method with only the reject handler passed in, i.e. then(null, rejectHandler)
. With the catch
method, exception in every link above it is handled by the catch
block.
Note that you may use the catch
method in the middle of a chain but this is not a good practice as any exceptions from the links below the catch
method are not taken care of.
Comparison
There are differences between the two methods. The most significant one is:
- in method 1, the exception is handled immediately in the chain. Once handled, the chain continues.
- in method 2, the exception is caught at the end of the chain and handled immediately but the links between the failed link and the
catch
block are skipped.
In most cases, you would like to skip the chain when an exception occurs because subsequent links in the chain rely on the results from the links above. In such cases, the catch
method will be beneficial.
However, sometimes, you may want the rest of the chain to continue to run regardless of an exception in a particular link. Or, you may need to handle the exception from a particular link differently from the rest of the chain. In these cases, defining a reject handler in the subsequent link will be useful. Note that both methods are not mutually exclusive. Therefore, a catch block can then be put at the end of the chain to handle any exceptions that might still happen after that link.
Conclusion
It is good practice to handle exceptions properly. In promises, there are two ways to handle exceptions: using reject handlers, and using the catch
method.
An exception in a chain can be handled by the first reject handler it sees down the chain or by a catch block at the end.
Author: Chia Jing Heng (andreusjh99)