Search code examples
javascriptnode.jsfunctionasynchronoussend

node.js result from asynchronous function call in server response


I'm sorry, this definitely has been answered before. I especially know about this thread here:

How do I return the response from an asynchronous call?

But to be honest, I'm still lost. I simply do not understand how to call a function asynchronously and include the return of this function in the response to the client.

I have a rather simple route (in routes.js) which shall provide content to a web page. A specific element of the site is a list of rfid tags. This list is populated from a function, which, of course I'd like and must call asynchron.

I'm using pug (formerly known as jade) as the template render engine and my tags.pug looks like this:

extends index.pug
block content 
  h2!= subheadline
  p!= messagetext
  p!= bodyContent

See the last p!=bodyContent? This element shall be the list of tags.

Below is a code snippet from my routes.js with the app.get('/tags') in which this site shall be returned:

// get the listing of all stored rfid tags
app.get("/tags", function(req, res) {
  res.render('tags', {
     title: 'RFID Tag Startseite',
     headline: 'RFID Tag Startseite',
     subheadline: 'Verfügbare Tags',
     messagetext: 'Bitte ein RFID Tag auswählen, um mehr Daten angezeigt zu bekommen',
     bodyContent: tagController.getTagList(app, function(tagController))
  });
})

Now, as you can see, I'm trying to call the function (getTagList) at the position bodyContent from a different module (tagController) and this shall provide the list of tags.

Following you can see the code of my getTagList() function:

var getTagList = function(app, result){
  // get global app variables
  var DEBUG = app.get('DEBUG');
  var svrAddr = app.get('svrAddr');
  var rfidTagDir = app.get('rfidTagDir');

  var responseContent = '';

    fs.readdir(rfidTagDir, function(err, items) {
      responseContent = "{\'response\': \'info\', \'message\': \'list of all stored rfid tags\', \'tags\': ["

        for (i in items) {
          var tag = items[i].toString().substring(0,items[i].indexOf('.'));
            responseContent += "{\'tag\': \'" + tag + "\', \'endpoint\': \'"+svrAddr+"/tags/tag/" + tag + "\', \'file\': \'"+items[i]+"\'}"
            if (i<items.length-1) responseContent += ",";
        }
        responseContent += "]}"
      if (DEBUG) console.log(responseContent);
      result = responseContent;
      return result;
  )};

My problem is, that it doesn't work. The code above gives the error:

 SyntaxError: Unexpected token )

For the last of the two ). If I try to populate a variable (say tagContent or whatever) prior to the res.send and include that variable to the response, I simply do not see the result of the function call. In the console.log, however, I can see the function being executed and the list of tags generated.

Can someone please, please, please tell me, how to call my function getTagList from module tagController from within my routes.js so that the content is displayed???

Kind regards,

Christian


Solution

  • So the problem is that getTagList will execute an async operation no matter what you are trying to return its not guaranteed that its correct ... so to be able to handle this you pass a callback function to getTagList which will be called inside the callback function of the async fs.readdir this is where you are 100% sure you have the correct result expected.

    So below is the updated getTagList function

    var getTagList = function(app, callback) {
        // get global app variables
        var DEBUG = app.get('DEBUG');
        var svrAddr = app.get('svrAddr');
        var rfidTagDir = app.get('rfidTagDir');
    
        var responseContent = '';
        fs.readdir(rfidTagDir, function(err, items) {
                if (err) {
                    callback(err);
                } else {
                    responseContent = "{\'response\': \'info\', \'message\': \'list of all stored rfid tags\', \'tags\': ["
    
                    for (i in items) {
                        var tag = items[i].toString().substring(0, items[i].indexOf('.'));
                        responseContent += "{\'tag\': \'" + tag + "\', \'endpoint\': \'" + svrAddr + "/tags/tag/" + tag + "\', \'file\': \'" + items[i] + "\'}"
                        if (i < items.length - 1) responseContent += ",";
                    }
    
                    responseContent += "]}"
                    if (DEBUG) {
                        console.log(responseContent);
                    }
                    //Here we are 100% sure we have the correct expected result so we call the callback function with correct data `responseContent`
                    callback(null, responseContent);
                }
            )
        };
    }
    

    Also you update your route to send the request only when data is available which is inside the callback function of getTagList, check code below

    app.get("/tags", function(req, res) {
        tagController.getTagList(app, function(err, result) {
            if (err) {
                console.log(err);
                //Handle error response
            } else {
                res.render('tags', {
                    title: 'RFID Tag Startseite',
                    headline: 'RFID Tag Startseite',
                    subheadline: 'Verf&uuml;gbare Tags',
                    messagetext: 'Bitte ein RFID Tag ausw&auml;hlen, um mehr Daten angezeigt zu bekommen',
                    bodyContent: result
                });
            }
        });
    })
    

    You can also notice how the callback functions are implemented in node ... we pass any error to the first argument, and the result to the second argument .. to have a better maintainable code.

    Finally you can see the same concept is actually used in the fs.readdir that already exist in your code you will see that you are using a callback function to be able to access items correctly.