Capturing an error within an asynchronous callback function

I am utilizing a callback function to asynchronously set some IPs in a Redis database.

My goal is to catch any errors that occur and pass them to my error handling middleware in Express.

To purposely create an error, I have generated one within the select method, but unfortunately, the error is not being caught as expected.

Take a look at the code snippet below:

module.exports = (req, res, next) => {
  const redis = require('redis')
  const client = redis.createClient()
  try {
    client.select('2d', (err) => { // By using '2d' instead of a number, an error is intentionally triggered
      const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
      client.set(ip, true, 'EX', 120, (err, rep) => {
        return next()
      })
    })
  } catch (err) {
    err.type = 'SilentSystem'
    next(err)
  }
}

Answer №1

According to the documentation of the redis npm package, it is apparent that it follows standard Node-style callbacks. In this callback style, the first argument passed to your provided callback function is either an error or null. This is where errors are reported. Errors cannot be caught by a try/catch block because by the time the error occurs, control has already moved out of the try/catch and the surrounding function.

To handle errors in this scenario, you would do something like this:

module.exports = (req, res, next) => {
  const redis = require('redis')
  const client = redis.createClient()
  client.select('2d', (err) => { // using '2d' string instead of number deliberately generates an error
    if (err) {
      // Error handling goes here
      err.type = 'SilentSystem'
      next(err)
    } else {
      // Success handling goes here
      const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
      client.set(ip, true, 'EX', 120, (err, rep) => {
        if (err) {
          err.type = 'SilentSystem'
          next(err)
        } else {
          next()
        }
      })
    }
  })
}

In a comment, you mentioned:

My actual code is a bit more complex so I was trying to avoid repeating calling if(err) and next(err) by using try. What's a better way (less verbose) to handle errors here?

This verbosity is inherent in Node-style callbacks. One approach could be to create a filtering function to pass all results through for centralized error handling.

Alternatively, you might consider utilizing a library that "promisifies" Node-style callbacks, allowing you to use promises with their chaining mechanism for centralized error handling. A package like `promisify` can help achieve this transformation. With promisified versions of client methods, the code could be simplified as follows:

module.exports = (req, res, next) => {
  const redis = require('redis')
  const client = makeNiftyPromiseVersionOf(redis.createClient())
  client.select('2d')
    .then(data => {
      const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
      return client.set(ip, true, 'EX', 120)
    })
    .then(() => {
      next()
    })
    .catch(err => {
      err.type = 'SilentSystem'
      next(err)
    })
}

Note how the consolidated error handling at the end simplifies the process. Any errors from client operations will flow through the catch block for handling.

This setup also allows for utilizing ES2017's async/await syntax for writing asynchronous code in a synchronous style:

module.exports = (req, res, next) => {
  (async () => {
    const redis = require('redis')
    const client = makeNiftyPromiseVersionOf(redis.createClient())
    try {
      const data = await client.select('2d');
      const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
      await client.set(ip, true, 'EX', 120)
      next()
    } catch (err) {
      err.type = 'SilentSystem'
      next(err)
    }
  })();
}

As a side note, it's recommended to move the require call outside of the exported function and perform it at the module level:

const redis = require('redis')
module.exports = {
  // ...
}

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

What is the best way to retrieve an object from every function?

I'm dealing with a <div> containing radio buttons: <div id='RB-01'> <span>Item_1</span><input type='radio' name='RB01' value='1'><br /> <span>Item_2</span><inp ...

Tips for directing the scroll according to the current selection class

I am attempting to focus the scroll based on the selected class when clicking on the previous and next buttons. How can I achieve this functionality? Please review my code on https://jsfiddle.net/kannankds/bcszkqLu/ <ul class="kds"> <li>tile ...

The embedded component is throwing an error stating that the EventEmitter is not defined in

Currently, I am delving into the realm of angular and facing an issue. The problem lies in emitting an event from a component nested within the main component. Despite my efforts, an error persists. Below is a snippet of my code: import { Component, OnIn ...

Modifying properties within child components

Within my parent Vue Page, I have inserted a FormInput component inside my form. new.vue <b-form @submit.prevent="submit"> <FormInput :name="name"/> <b-button @click="submit">Save</b-button> <b-form> <script> i ...

Updating reactive form fields with patched observable data in Angular

Struggling with initializing a reactive form in my component's onInit() method, as well as handling data from a service to update or create entries in a MySQL table. The issue lies in using patchValue to pass the retrieved data into the form: compone ...

Attempting to activate template rendering with Meteor session

I'm currently facing an issue with Meteor sessions and how my code is triggering the rendering of a template. At the moment, I have a session that sets its ._id to whatever is clicked. Template.sidebar.events({ /* on click of current sidecat class ch ...

Can you clarify the purpose of response.on('data', function(data) {}); in this context?

I am curious about the inner workings of response.on("data"). After making a request to a web server, I receive a response. Following that, I utilize response.on("data" ...); and obtain some form of data. I am eager to uncover what kind of data is being p ...

When the page is loaded, populate FullCalendar with events from the model

On page load, I am attempting to populate events with different colors (red, yellow, green) on each day of the calendar. Here is a simple example showcasing events for three days: I have data in a model that indicates the available amount of free pallets ...

Canvg | Is there a way to customize canvas elements while converting from SVG?

Recently, I encountered an issue with styling SVG graphics dynamically generated from data. The SVG graphic appears like this: To address the problem, I turned to Canvg in hopes of converting the SVG into an image or PDF using a hidden canvas element. How ...

Could not locate the command "express" - perhaps it is not installed

I've tried multiple times to install Express, but all I get is the error mentioned in the title. Any assistance would be greatly appreciated. Here are the commands I have attempted. While my current directory is where I want the application to be loc ...

Issue with wp_ajax function not being triggered in Wordpress

Attempting to implement my first AJAX Function in Wordpress 4.3. I created a plugin called "calipso". Within the plugin, there are two files: calipso.php : <?php /** * @package calipso * @version 1.0 */ /* Plugin Name: calipso Plugin URI: http://www. ...

Next.js is throwing an unhandled runtime error of type TypeError, which is causing issues with reading properties of null, specifically the 'default' property

"use client" import { useKindeBrowserClient } from '@kinde-oss/kinde-auth-nextjs' import Image from 'next/image'; import React from 'react' function DashboardHeader() { const {user} = useKindeBrowserClient(); r ...

The default expansion behavior of Google Material's ExpansionPanel for ReactJS is not working as expected

Currently, I am in the process of developing a ReactJS application that utilizes Google's Material-UI. Within this project, there is a child class that gets displayed within a Grid once a search has been submitted. Depending on the type of search con ...

Vue component lifecycle hook to fetch data from Firebase

Looking for a solution with Vue 2 component that utilizes Vuefire to connect declaratively with a Firebase real-time database: import { db } from '../firebase/db' export default { data: () => ({ cats: [] }), firebase: { ...

Using Vue and Vuex to wait for asynchronous dispatch in the created hook

I'm struggling to implement asynchronous functionality for a function that retrieves data from Firebase: Upon component creation, I currently have the following: created(){ this.$store.dispatch("fetchSections"); } The Vuex action looks ...

Updating a table in Javascript after deleting a specific row

Is there a way to automatically reindex rows in a table after deleting a row? For example, if I delete row 1, can the remaining rows be reordered so that they are numbered sequentially? function reindexRows(tableID) { try { var t ...

How to retrieve the latest document from every sender within a JavaScript array using Mongoose/MongoDB queries

I recently created a schema: var messageSchema = mongoose.Schema({ sender:String, recipient:String, content:String, messageType: Number, timestamp: {type: Date, default: Date.now} }); Next, I defined a model for this schema: var Mess ...

Warning: The core schema has detected an unknown property `color` for the component or system `undefined` in Aframe + Vuejs. This issue was flagged within 10 milliseconds in

I am facing some challenges trying to integrate Aframe and vuejs seamlessly, as the console is displaying warning messages. It seems like Aframe is validating the attribute values before vue has a chance to modify them. Warning messages core:schema:warn ...

Integrating a search box with radio buttons in an HTML table

I am currently working on a project that involves jQuery and a table with radio buttons. Each button has different functionalities: console.clear(); function inputSelected(val) { $("#result").html(function() { var str = ''; ...

Require assistance with refreshing the index for the chosen row

I have encountered a problem while attempting to manipulate table rows using Javascript. Adding new rows works fine, but deleting rows presents an issue. Specifically, the delete function fails if the first row or a row in the middle is deleted (the live ...