Describing Error Handling

Objective

After completing this lesson, you will be able to explain error handling

Error Handling - CAP Service SDK for Node.js

Why We Need This?

Having good error handling is key to ensuring the robustness, correctness, and performance of the given application. Building robust applications requires you to throw and handle exceptions, which occur during the runtime of the application. Therefore, you are introduced to the basic concepts of exception handling in Node.js and the specific techniques for the CAP Service SDK for Node.js.

Error Handling and Error Types

Proper error handling is crucial for today's business applications. Before going into more detail, it is necessary to distinguish between two types of errors:

Programmer Errors
These occur as a result of programming errors (for example, cannot read 'foo' of undefined). They must be fixed.
Operational Errors
These occur during runtime (for example, when sending a request to a faulty remote system). They must be handled.

Guidelines

"Let it crash" is a philosophy taken from the Erlang programming language (Joe Armstrong), which is also (partially) applicable to Node.js.

The key takeaways for programming errors are:

  • Fail loudly: Do not hide errors and continue silently. Ensure to log unexpected errors correctly. Don't catch errors you can't handle.
  • Don't develop in a defensive fashion: Focus on your business logic and only handle errors when you know they will occur. Use try/catch blocks only when necessary.

Never try to catch and handle unexpected errors, rejections of promises, and so on. If it is unexpected, you cannot handle it correctly. If you could, it would be expected (and should already be handled). Even if your apps are stateless, you can never be 100% sure that a shared resource was not affected by the unexpected error. Therefore, you never allow an app to continue running after such an event, especially for multitenant apps where there is a risk of information disclosure.

Following these guidelines will make your code shorter, clearer, and simpler.

Never Hide the Causes of Errors

When an error occurs, it is essential to identify the root cause. The CAP SDK for Node.js, for instance, throws exceptions when certain conditions, such as a CRUD operation violating foreign key constraints, are met. In such cases, the framework raises a specific exception, like UNIQUE_CONSTRAINT_VIOLATION. However, if these errors are caught and rethrown without retaining the original details, it becomes challenging to trace and resolve the underlying issue. As a result, the end user is left with only a cryptic error message, making troubleshooting difficult.

For example:

Screenshot of an error message which reads 'Unique constraint violation'.
JavaScript
123456789
try { // something } catch (e) { // augment instead of replace details e.message = 'Oh no! ' + e.message e.additionalInfo = 'This is just an example.' // re-throw same object throw e }

It is therefore useful to provide a meaningful error message.

For this purpose, you can register an error handler in your service implementation, as exemplified in the following snippet.

JavaScript
12345678910111213141516171819202122232425262728
// Imports constcds=require("@sap/cds"); /** * The service implementation with all service handlers */ module.exports=cds.service.impl(asyncfunction(){ /** * Custom error handler * * throw a new error with: throw new Error('something bad happened'); * **/ this.on("error",(err,req)=>{ switch(err.message){ case"UNIQUE_CONSTRAINT_VIOLATION": err.message="The entry already exists."; break; default: err.message= "An error occured. Please retry. Technical error message: "+ err.message; break; } }); });

This handler now steps in whenever this exception gets triggered and overrides it with an alternative error message:

Screenshot of an error message which reads 'The entry already exists'.

Raising and Catching Exceptions

You will certainly add your implementations to your services. It is likely that you want to interrupt some operations before something crashes. In this case, you can throw a Node.js exception.

Request Response

You can also use the req.error() method to collect messages or errors and return them to the caller in the request-response. Read more here.

JavaScript
12345678910111213
this.on("submitOrder",async(req)=>{ const{ book, amount }=req.data; let{ stock }=awaitdb.read(Books,book,(b)=>b.stock); if(stock >= amount){ awaitdb.update(Books,book).with({stock: (stock-=amount)}); awaitthis.emit("OrderedBook",{ book, amount,buyer: req.user.id}); returnreq.reply({ stock });// <-- Normal reply }else{ // Reply with error code 409 and a custom error message returnreq.error(409,`${amount} exceeds stock for book #${book}`); } });

Summary

The core error handling concepts in the CAP SDK for Node.js are now familiar to you. We strongly recommend incorporating these concepts to ensure the overall robustness of your CAP application.

Log in to track your progress & complete quizzes