Blog

Handling Errors Using Recursive Extraction in JavaScript

Have you ever felt lost in a web of error messages, struggling to pinpoint the root cause of an issue? Extracting meaningful information from nested error objects can be a real challenge. Thankfully, JavaScript offers powerful tools like recursion to navigate complex data structures. In this blog post, we'll delve into a recursive extraction function that helps you dynamically extract error messages from intricate error responses.

The Problem: Nested Error Objects and Hidden Messages

Imagine receiving an error response from an API or a response from the apex controller, but the error message is buried deep within layers of nested objects. Manually traversing this structure to find the message can be tedious and error-prone. This is where our recursive extraction function comes into play.

The Solution: Recursive Extraction Function

The following code snippet showcases a recursiveExtract function designed to tackle this challenge. Let's break down its functionality step-by-step:

  • Iterating Through the Object: The function iterates over every key-value pair in the provided object using a for…in a loop.
  • Handling Arrays: If the value is an array, the function uses the forEach method to iterate through each element and apply the recursiveExtract function again, ensuring it reaches every corner of the data structure.
  • Diving into Objects: When the value is an object (excluding null values), the function recursively calls itself with that object, continuing the exploration deeper into the nested structure.
  • Extracting the Message: If the key encountered is named "message," the function assumes it holds the desired error message and assigns it to a variable (not shown in the code snippet).

Benefits and Applications:

  • Handles Complex Structures: The recursive nature ensures the function can efficiently navigate objects with any level of nesting.
  • Dynamic Extraction: The function dynamically extracts the message based on the key name, making it adaptable to various error response formats.
  • Improved Code Maintainability: By encapsulating the logic in a reusable function, your code becomes more readable and easier to maintain.

Code Snippet:

Sample error response:

{"status":500,"body":{"exceptionType":"System.QueryException","isUserDefinedException":false,"message":"List has no rows for assignment to SObject","stackTrace":"Class.ErrorHandlingController1.listHasNoRowsError: line 4, column 1"},"headers":{},"ok":false,"statusText":"Server Error","errorType":"fetchResponse"}

Example for Aura:
Controller js:

({
    listHasNoRowsError: function(component) {
        var action = component.get("c.listHasNoRowsError"); // Apex method call
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.account", response.getReturnValue());
            } else if (state === "ERROR") {
                var errors = response.getError();
                var errorMessage = handleError(errors);
                console.log('errorMessage: ' + errorMessage); // "List has no rows for assignment to SObject"
            }
        });
        $A.enqueueAction(action);
    },

    handleError: function(obj) {
        var errorMessage;
        if (!Array.isArray(obj)) {
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    if (Array.isArray(obj[key])) {
                        obj[key].forEach(function(element) {
                            var result = handleError(element);
                            if (result) {
                                errorMessage = result;
                            }
                        }, this);
                    } else if (typeof obj[key] === 'object' && obj[key] !== null) {
                        var result = handleError(obj[key]);
                        if (result) {
                            errorMessage = result;
                        }
                    } else if (key === 'message') {
                        errorMessage = obj[key];
                    }
                }
            }
        } else {
            obj.forEach(function(element) {
                var result = handleError(element);
                if (result) {
                    errorMessage = result;
                }
            }, this);
        }
        return errorMessage;
    }
})

Example for LWC:
LWC Controller:

connectedCallback() {
        listHasNoRowsError()
        .then(result=>{
            this.account = result;
        })
        .catch(error => {
        this.error = error;
            let errorMessage = this.handleError(error);
            console.log('errorMessage' + errorMessage); //"List has no rows for assignment to SObject
      });
}
handleError(obj) {
        let errorMessage;
        if(!Array.isArray(obj)){
            for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                if (Array.isArray(obj[key])) {
                    // If it's an array, recursively extract from each element
                    obj[key].forEach(element => {
                        this.handleError(element);
                    });
                } else if (typeof obj[key] === 'object' && obj[key] !== null) {
                    // If it's an object, recursively extract from the object
                    this.handleError(obj[key]);
                } else if (key === 'message') {
                    errorMessage = obj[key];
                }
            }
        }
        } else {
            obj.forEach((obj) => {
                        this.handleError(obj);
                    });
        }
        return errorMessage;
    }