Implement error handling with the try/catch function when utilizing transaction sessions within the wrapper

Performing multiple database actions within functions can be complex, especially when handling failures. That's why I utilize a transaction session from Mongoose to easily revert any actions if needed.

To begin, I initialize a session using the startSession function and incorporate it into various Model.create functions. Finally, I commit and end the session at the function's conclusion.

Given that I use an asyncHandler wrapper across all my functions, I want to streamline the process of introducing the session into the asyncHandler or another wrapper to manage transaction rollback in case of errors during these functions.

Example of Register Function

import { startSession } from 'mongoose';
import Company from '../models/Company';
import Person from '../models/Person';
import User from '../models/User';
import Mandate from '../models/Mandate';
import asyncHandler from '../middleware/asyncHandler';

export const register = asyncHandler(async (req, res, next) => {    
    const session = await startSession();
    
    let entity;

    if(req.body.profile_type === 'company') {
        entity = await Company.create([{ ...req.body }], { session });
    } else {
        entity = await Person.create([{ ...req.body }], { session });
    }

    // Create user
    const user = await User.create([{ 
        entity,
        ...req.body
    }], { session });

    // Create mandate
    await Mandate.create([{
        entity,
        status: 'unsigned'
    }], { session });

    // Generate user account verification token
    const verification_token = user.generateVerificationToken();

    // Send verification mail
    await sendAccountVerificationMail(user.email, user.first_name, user.language, verification_token);

    await session.commitTransaction();
    session.endSession();

    res.json({
        message: 'User succesfully registered. Check your mailbox to verify your account and continue the onboarding.',
    })
});

asyncHandler Helper Function

const asyncHandler = fn => ( req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);

export default asyncHandler;

EDIT 1

To clarify, I'm seeking a solution (be it through one or more wrapper functions or an alternative method) to eliminate the repetition of lines containing // ~ repetitive code. This includes managing try/catch blocks and the initialization and termination of a database transaction.

export const register = async (req, res, next) => {    
    const session = await startSession(); // ~ repetitive code
    session.startTransaction(); // ~ repetitive code

    try { // ~ repetitive code     
        let entity;

        if(req.body.profile_type === 'company') {
            entity = await Company.create([{ ...req.body }], { session });
        } else {
            entity = await Person.create([{ ...req.body }], { session });
        }

        const mandate = await Mandate.create([{ entity, status: 'unsigned' }], { session });

        const user = await User.create([{ entity, ...req.body }], { session });
        const verification_token = user.generateVerificationToken();
        
        await sendAccountVerificationMail(user.email, user.first_name, user.language, verification_token);

        await session.commitTransaction(); // ~ repetitive
        session.endSession(); // ~ repetitive

        res.json({
            message: 'User succesfully registered. Check your mailbox to verify your account and continue the onboarding.',
        });
    } catch(error) { // ~ repetitive
        session.abortTransaction(); // ~ repetitive
        next(error) // ~ repetitive
    } // ~ repetitive
};

Answer №1

By encapsulating repetitive code within a class, it becomes easier to manage:

class TransactionHandler {
  async middleware(req, res, next) {
    const session = await startSession();
    session.startTransaction();
    try {
      await this.executeTransaction(req, session);
      await session.commitTransaction();
      session.endSession();
      this.sendResponse(res);
    } catch (error) {
      session.abortTransaction();
      next(error);
    }
  }
  async executeTransaction(req, session) { }
  sendResponse(res) { }
}

This allows for the creation of subclasses that inherit the base functionality:

class RegisterTransaction extends TransactionHandler {
  async executeTransaction(req, session) {
    let entity;
    if (req.body.profile_type === 'company') {
      entity = await Company.create([{ ...req.body }], { session });
    } else {
      entity = await Person.create([{ ...req.body }], { session });
    }
    const mandate = await Mandate.create([{ entity, status: 'unsigned' }], { session });
    const user = await User.create([{ entity, ...req.body }], { session });
    const verification_token = user.generateVerificationToken();
    await sendAccountVerificationEmail(user.email, user.first_name, user.language, verification_token);
  }
  sendResponse(res) {
    res.json({
      message: 'User registered successfully. Please check your email to verify your account and complete the onboarding process.',
    });
  }
}
export const handleRegisterTransaction = async (req, res, next) => {
  new RegisterTransaction().middleware(req, res, next);
}

Answer №2

The asynchronous handler logic you're using seems familiar to what's discussed here. If it's not from there, combining that with information found in this article about res.locals should help answer your question.

Based on the assumption that you're working with Express, I recommend focusing the tags on javascript and express only as this issue primarily pertains to those areas.

I didn't mark this as a duplicate because during my research, I came across details regarding Express 5. Notably, starting with Express 5, route handlers and middleware that return Promises will automatically trigger next(value) when encountering an error or rejection. This allows for easier error handling within the framework.

In essence, with Express 5, you can simplify error handling by utilizing code like the following:

app.get('/user/:id', async (req, res, next) => {
  const user = await getUserById(req.params.id)
  res.send(user)
})

If any errors occur within the Promise returned by getUserById, Express will handle them behind the scenes, passing control to an error handler such as:

app.use((err, req, res, next) => {
    console.log(err);
});

Edit for OP's revision

This issue revolves around programming patterns. My suggestion is to explicitly include individual try..catch, startSession, and abortTransaction blocks within each database function like you have done.

An alternative approach would be implementing shared error handling among these functions to enhance maintainability and debugging capabilities.

  • Huge try...catch blocks can complicate debugging and impede precise error handling. Splitting them into smaller segments allows for better control over exception scenarios.

  • Avoid unnecessary use of transactions to prevent performance overhead and potential deadlock issues. Consider creating utility functions to scope transactions effectively if required.

Overall, while consolidating code under one try...catch block may eliminate duplication, it's vital to acknowledge situations where repeating sections of code may be beneficial for clarity and troubleshooting purposes. Duplicate code isn't always detrimental!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ongoing Activity Triggered by jquery.blur() Function Currently Underway

I am experiencing a peculiar behavior with an input field: <input type="text" id="myId" ns-validate="" class="someClass" ng-model="Date" ng-focus="focus($event, 'Parameters')" ng-blur="blur($event)" tabindex="-1"> The focus event is linke ...

Obtaining the Span value inside a DIV when the text in the DIV is selected - JavaScript

I am dealing with a series of div elements structured in the following way: <div id="main1"> <span class="username">Username_1</span> white some text content inside div... blue some text content inside div... yellow some ...

Guide to cycling through an array in Angular template in order to display assorted colored tiles with a consistent pattern

View images in tile format Here are the specific colors assigned to each tile when looping through 0 to 11, where there are 12 records in the array for each tile: - Red color for tiles at index: 0, 4, 8, and 12 - Green color for tiles at index: 1, 5, an ...

Keep moving forward in Sailsjs even after sending back a response with res.json();

It will keep running even if the condition is met and the code inside return res.json() gets executed. _.each(rooms, function(room){ if(room.users.length == users.length) { return res.json(room); // <-- returns but execution continues } ...

Combining collections using the "_id" field in MongoDB: A step-by-step guide

Greetings, I am currently in the process of merging two collections. Products Categories The only connection between them is the ObjectId of the corresponding document. Take a look: PRODUCT COLLECTION { "_id": Object(607a858c2db9a42d1870270f), ...

I have implemented a Node.js promise to check if a username exists in the database or not

Can anyone guide me on how to check if a username exists in MongoDB using promises? I'm new to Node.js and would appreciate help in understanding the process. Thanks in advance. var errorsArr = []; var promise = checkUsername(); promise.then(function ...

How to dynamically add an input textbox after a selection change in YUI JavaScript?

Is there a way to add an extra input textbox after an onchange event in a selectbox using the YUI library? I need to utilize YUI and the form is generated with PEAR Quickforms lib. This is what I have so far: $form->addElement('select', &ap ...

I have incorporated jquery-1.2.6.js into my project but I am encountering difficulties utilizing the live method

C# foreach (DataRow Row in oDs.Tables[0].Rows) { LitPreferances.Text += "<Li ID=LI_" + Row["pk_Preference_Branch_ID"].ToString() +"_"+ Row["pk_Preference_BranchType_ID"].ToString() +">" + Row["Branch_Name"].ToString() + "&nbsp;&nbsp;< ...

Generating numerous circular elements for each item in the dataset using D3

My dataset consists of various years and corresponding numerical values for each year as shown below: "data":[ ["1951","306","27","159","34","82","4"], ["1956","426","41","203","47","119","16"], ["1959","562","67"," ...

ReactKonva does not have compatibility with the element type "div"

I am trying to create a circle with ReactKonva and display a tooltip when hovering over it. In React, we are required to return a single element from the render method. To achieve this, I attempted to wrap both the tooltip and the ReactKonva element within ...

Using JavaScript and the Firefox browser, learn how to easily highlight an element with Selenium-WebDriver

I am struggling with creating a valid function to highlight specific elements on a webpage. As a beginner in coding, I suspect that the issue may either be related to my environment setup or a lack of knowledge about JavaScript/Selenium features. I am wri ...

Steps to fix the Error: connect EISCONN ::1:5555 - Local (:ffff.127.0.0.1:5555)

Currently, I am in the process of developing an Electron app where I need to establish a connection with a TCP port on my local machine. However, upon starting the application, I encounter the following error message: "A JavaScript error occurred in the ma ...

Practical tips for sending an array of objects via RESTful query strings?

What is the most effective way to include an array of objects in a query string using REST principles? Consider the following array: examples[] = [ { name: "foo", value: "1" }, { name: "bar", value: &qu ...

Having trouble with setting up ENV variables while deploying the app on Heroku

I recently deployed my node.js app to Heroku, and I'm facing some issues with its functionality. The app loads fine, but when I try to sign in, it breaks down. It seems like the environment variables are not loading correctly, specifically the db vari ...

What steps can be taken to resolve the issue of the <td> element not being allowed as a child of an <a> tag?

https://i.stack.imgur.com/nsdA7.png How can I address these warnings? I utilized the material UI table component and suspect that the warnings are originating from component={Link} to={/patient/${patient.id}} <TableContainer className={styles.tableCo ...

jqgrid now features inline editing, which allows for the posting of only the data that

My jqGrid includes editable columns, and I am looking for a way to only post the data of columns where changes have been made. Here is an example of my colModel: colModel: [{ name: 'I_PK', index: 'u.I_PK ...

Customize your Vue 3 application with custom Axios functions for handling GET, PUT,

I am looking to enhance my use of axios by customizing the get, post, and put functions. After performing the axios.create() operation, I want every subsequent get operation to include a then and catch block. import axios from "axios"; export de ...

What steps do I need to take to modify the MUI Badge component and insert custom text inside?

Is there a way to replace the number with a label next to a new row added to my table using MUI Badge? For example, instead of displaying a number like 4, I want it to show the word "New" as shown in this image: enter image description here This is the co ...

enable jQuery timer to persist even after page refresh

code: <div class="readTiming"> <time>00:00:00</time><br/> </div> <input type="hidden" name="readTime" id="readTime"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script&g ...

When accessing a page from a link, JavaScript sometimes does not immediately execute on the first attempt

I'm encountering a strange issue in my rails application, where a template fails to execute the Javascript code the first time it is loaded via a link on my home page. This problem only occurs when accessed through the link for the first time. I' ...