Preventing the callback nightmare in nodeJs / Sharing variables with nested functions

Let's simplify this scenario:

const generateUrl = (req, res) => {
    const id = req.query.someParameter;

    const query = MyMongooseModel.findOne({'id': id});
    query.exec((err, mongooseModel) => {
        if(err) {
            //handle error
        }

        if (!mongooseModel) {
            generateUrl(id, (err, text, url) => {
                if (err) {
                    res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
                    return;
                }
                const newMongooseModel = new AnotherMongooseModel();
                newMongooseModel.id = id;

                newMongooseModel.save((err) => {
                    if (err) {
                        res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
                    } else {
                        res.send({url: url, text: text});
                    }
                });
            });
        } else {
            //handle existing model
        }
    });
};

I've read other answers suggesting the use of named functions, but they don't address passing variables or using jQuery's queue. I'm unable to utilize either in my code.

I know that replacing anonymous functions with named ones is an option, but I'm unsure how to manage variables that need to be passed around. For example, how would an inner function access the res variable if it's defined elsewhere?

Answer №1

Your question centers around a crucial concept:

You are aware that you can substitute your anonymous functions with named functions, but then the challenge arises of passing variables around. How would an inner function access 'res' for example if the function is defined elsewhere?

The solution lies in using a function factory.

In essence, transforming this:

function x (a) {
    do_something(function(){
        process(a);
    });
}

can be achieved by converting it to this:

function x (a) {
    do_something(y_maker(a)); // take note that we're calling y_maker,
                              // rather than passing it as a callback
}

function y_maker (b) {
    return function () {
        process(b);
    };
}

In the provided code snippet, y_maker is a function that produces another function named 'y'. In my personal code, I utilize the naming convention of .._maker or generate_.. to indicate that I'm utilizing a function factory. However, this is merely a personal preference and not a widely adopted standard.

Therefore, for your code, you can refactor it as follows:

exports.generateUrl = function (req, res) {
    var id = req.query.someParameter;

    var query = MyMongooseModel.findOne({'id': id});
    query.exec(make_queryHandler(req,res));
};

function make_queryHandler (req, res) {
    return function (err, mongooseModel) {
        if(err) {
            //handle error
        }
        else if (!mongooseModel) {
            generateUrl(Id,make_urlGeneratorHandler(req,res));
        } else {
            //handle existing condition
        }
}}

function make_urlGeneratorHandler (req, res) {
    return function (err, text, url) {
        if (err) {
            res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
            return;
        }
        var newMongooseModel = new AnotherMongooseModel();
        newMongooseModel.id = id;
        newMongooseModel.save(make_modelSaveHandler(req,res));
}}

function make_modelSaveHandler (req, res) {
    return function (err) {
        if (err) res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
        else res.send({url: url, text: text});
}}

This restructuring eliminates the nested callbacks and provides the added benefit of clearly defining the purpose of each function, which is considered a good coding practice.

Furthermore, employing named functions in this manner results in significantly faster execution compared to using anonymous callbacks (whether through nested callbacks or promises – although using named functions with promise.then() yields similar speed enhancements). An earlier Stack Overflow inquiry highlighted that named functions in node.js are more than twice as fast (if memory serves me right, it was over 5 times faster) than anonymous functions.

Answer №2

Consider utilizing promises in your code. By incorporating Q and mongoose-q, you can achieve something similar to the following:

exports.generateUrl = function (req, res) {
    var id = req.query.someParameter;
    var text = "";

    var query = MyMongooseModel.findOne({'id': id});
    query.execQ().then(function (mongooseModel) {

        if (!mongooseModel) {
            return generateUrl(Id);
     }).then(function (text) {
         var newMongooseModel = new AnotherMongooseModel();
         newMongooseModel.id = id;
         text = text;

         newMongooseModel.saveQ()
     }).then(function (url) {
         res.send({url: url, text: text});
     }).fail(function(err) {
         res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
     });
};

Answer №3

When it comes to executing named functions, they operate within the same scope as anonymous functions and have access to all variables currently in use. While this method can make your code less nested and more readable (a definite plus), it still falls under the category of "callback hell". To steer clear of such scenarios, consider wrapping your asynchronous libraries with a promise library like Promise Library. In my opinion, promises offer a clearer depiction of the execution flow.

To avoid ambiguity regarding variable origins, you can bind parameters to your named function using the bind method. Here's an example:

function handleRequest(res, err, text, url) {
    if (err) {
        res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
        return;
    }
    var newMongooseModel = new AnotherMongooseModel();
    newMongooseModel.id = id;

    newMongooseModel.save(function (err) {
        if (err) {
            res.status(HttpStatus.INTERNAL_SERVER_ERROR).send(err);
        } else {
            res.send({url: url, text: text});
        }
    });
}

...
generateUrl(Id, handleRequest.bind(null, res));

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

Developing a table with JavaScript by parsing JSON data

Starting off, I am relatively new to working with JavaScript. Recently, I attempted to generate a table using data from a JSON file. After researching and following some tutorials, I successfully displayed the table on a web browser. However, I noticed tha ...

Exploring ways to create simulated content overflow using react-testing-library

I have integrated material-table with material ui to develop a spreadsheet application. One of the features I have added is setting a maximum width of 50px for cells. If the content in a cell exceeds this width, it will display an ellipsis at the end of ...

Unable to refresh HTML table using Vuex data in Vue JS

I am new to working with Vue.js and particularly Nuxt. I am facing an issue with updating a table using data fetched from the backend. Even though I can see the data in the Vuex tab when the page loads, I cannot seem to populate the table with it. The func ...

Interacting with a third-party application via OAuth using Node server to send REST requests

https://i.stack.imgur.com/Znrp0.png I've been working on a server to manage chat messages and need to integrate with a JIRA instance. I'm currently using the passport-atlassian-oauth strategy for authentication and BearerStrategy for requests. H ...

Using Jest to mimic API requests in a NodeJs environment

Can someone assist me with testing this code? adapter.js async function adapt(message) { let parser = JSON.parse(message.content.toString()); let apiResult = await api(parser.id); let result = apiResult.data.data; return adappt ...

What could be the reason my code isn't successfully performing addition within the input field?

As a novice, I am practicing by attempting to retrieve a number from a text field, prompting the user to click a button that adds 2 to that number, and then displaying the result through HTML. However, I keep encountering an issue where NaN is returned whe ...

Enhance an existing webpage by integrating ajax with jquery-ui

I'm seeking assistance with integrating an advanced search feature into an existing webpage system. It appears that there is a complication with the current jQuery UI being used on the page: <script src="js/jquery-ui-1.10.4.custom.js"> ...

Get rid of numerous div elements in a React.js application with the help of a Remove button

I need help figuring out how to efficiently remove the multiple div's that are generated using the add button. I am struggling to grasp how to pass the parent div's id into the delete method from the child div. Additionally, I'm wondering if ...

Clearing a leaflet layer after a click event: Step-by-step guide

When working with my map, I attempt to toggle the layer's selection using a mouse click. Initially, my map looks like this: Upon clicking a layer, my goal is to highlight and select it: If I click on a previously selected layer again, I want to dese ...

Securing and Authorizing API Access

Seeking guidance on how to approach the following situation and implement best practices. Our organization is looking to revamp its outdated IT systems and develop new website applications as well as potentially mobile apps for employees and contractors t ...

Setting a checkbox to true within the MUI Data Grid using my input onChange event - how can I achieve this?

I am looking to highlight the selected row in my usage input onChange event. https://i.stack.imgur.com/rp9rW.png Details of Columns: const columns = [ { field: 'part_code', headerName: 'Part Code', width: 200 }, { ...

Using inertia-links within the storybook framework

Hey there, just wanted to share my experience with storybook - I'm really enjoying it so far! Currently, I'm facing a challenge while implementing it in my Laravel app with Inertia. I'm trying to render a navigation link component that make ...

Struggling with implementing private npm modules using require() and import

Trying to import a private npm module hosted in sinopia, I can see it available in the sinopia folder structure. I successfully installed the module via "npm install --save", and it seems to pick it up from the local registry. However, when attempting to ...

What steps can be taken to manage obsolete packages that are needed by a dependency in the package-lock.json file?

Imagine having a dependency called some_dep that has a security vulnerability. The guidance provided on GitHub is to “Upgrade some_dep to version 2.2.3 or newer.” But things become complex when you realize that some_dep is a necessary dependency of an ...

The window.onload function is ineffective when implemented on a mail client

Within my original webpage, there is a script that I have created: <script> var marcoemail="aaaaaa"; function pippo(){ document.getElementById("marcoemailid").innerHTML=marcoemail; } window.onload = pippo; </script> The issue a ...

I am having difficulty accessing the dataset on my flashcard while working with React/Next JS

I'm currently developing a Flashcard app that focuses on English and Japanese vocabulary, including a simple matching game. My goal is to link the two cards using a dataset value in order to determine if they match or not. When I click on a flashcar ...

Utilizing tag keys for inserting text and adjusting text sizes within a Web application

Creating an online editing interface for coursework where keyboard events are used. The goal is to have the tab key insert text, while also reducing the size of the text on that line. However, upon using getElementById, an error message pops up stating: ...

Array contains a copy of an object

The outcome I am striving for is: dataset: [ dataset: [ { seriesname: "", data: [ { value: "123", }, { value: &q ...

Utilize Node.js to call the Windows SetWinEventHook function

I'm attempting to utilize the SetWinEventHook function in a C# environment, following the instructions provided for C# on this source. However, I am trying to implement it in nodejs. To achieve this, I am using the ffi-napi library for binding. Below ...

What is the best way to trigger the onclick event before onblur event?

I have two elements - an anchor with an onclick function and an input with an onfocus event. The anchor is toggled by the input button; meaning, when the button is in focus, the anchor is displayed, and when it loses focus, the anchor is hidden. I'm l ...