How can I make a callback that requires info of its child function

515 views Asked by At

I am working in a project that uses Node.js for a Haraka (an smtp server) plugin.

This is Node.JS and I have a little problem whith callbacks. I haven't been able to convert this particular code to use a callback.

So, this is the code that I have:

exports.hook_data = function (next, connection) {
    connection.transaction.add_body_filter('', function (content_type, encoding, body_buffer) {
        var header = connection.transaction.header.get("header");
        if (header == null || header == undefined || header == '') return body_buffer;

        var url = 'https://server.com/api?header=' + header ;
        var request = require('request');
        request.get({ uri: url },
          function (err, resp, body) {
              var resultFromServer = JSON.parse(body);
              return ChangeBuffer(content_type, encoding, body_buffer, resultFromServer);
          }
        );
    });
    return next();
}

This code does not work because It doesn't wait the callback of the Request to continue. I need to finish the request before next();

And these are the requirements:

  1. At the end of exports.hook_data is mandatory to return next(). But it only have to return it after the request.
  2. I need to return a Buffer in add_body_filter but I need to create the buffer with information getted from a server.
  3. To make the Get Request I need to use a parameter (header) that I only have inside add_body_filter.

So the issue is that I can not make the request before add_body_filter and put the result inside a callback because the parameter that I need to make the request are only inside add_body_filter.

Any advice please?

5

There are 5 answers

0
Hurricane Hamilton On BEST ANSWER

Unless you are willing to use synchronous, blocking functions, it is impossible to satisfy the requirements you have numbered.

I would examine reasons underlying each requirement and see if you accomplish your goals in a different way.

On face value, looking at just the code you have there, I would look for a way to alter the contract of exports.hook_data so that you're able to call next() from inside the request.get callback.

0
Quy On

Use Async.js or Promises.

Async implementation:

exports.hook_data = function (next, connection) {
  async.waterfall([
    function( done ) {
      connection.transaction.add_body_filter('', function( content_type, encoding, body_buffer ) {
        var header = connection.transaction.header.get("header");
        if ( header == null || header == undefined || header == '' ) {
          done(null, body_buffer);
        }
        done(null, header);
      });
    },
    function( header, done ) {
      var url = 'https://server.com/api?header=' + header;
      var request = require('request');
      request.get({ uri: url },
        function( err, resp, body ) {
          // do something if there's an error
          var resultFromServer = JSON.parse(body);
          done(null, ChangeBuffer(content_type, encoding, body_buffer, resultFromServer));
        }
      );
    }
  ], function( error, buffer ) {
    // do something if there's error
    next(buffer);
  });
}

There's a lot here and I recommend you reading the docs on async.js#waterfall. Essentially it breaks up each async call into it's on function block and waits for it to return before it continues onto the next block.

Again, since these are all async, Node submits these tasks to the IO event loop and it's taken care of there (non-blocking). When this thread is waiting, it'll probably move on to the next request while this request is waiting.

1
Vivek Athalye On

Looking at the Haraka manual for Transaction object & Header object I don't see there is any dependency of Header on add_body_filter. Also your code doesn't show any dependency on add_body_filter. So your 3rd requirement looks invalid.

Considering that, I think following pseudocode(as I can't test it) should work for you.

exports.hook_data = function (next, connection) {
    var header = connection.transaction.header.get("header");
    if (header == null || header == undefined || header == '') {
        return next();

    var url = 'https://server.com/api?header=' + header ;
    var request = require('request');
    request.get({ uri: url },
        function (err, resp, body) {
            var resultFromServer = JSON.parse(body);
            connection.transaction.add_body_filter('', function (content_type, encoding, body_buffer) {
                return ChangeBuffer(content_type, encoding, body_buffer, resultFromServer);
            });
            next();
        }
    );
}

If Haraka manual has failed to highlight the dependency of Header on add_body_filter and / or you have discovered it based on your practical experience, then Quy's approach seems to be the way to go.

If you are wondering when to use next() v/s return next()

2
AudioBubble On

For handling priority of each part of your code I use Q, You can use it in few steps:

  1. npm install q.

    var request = require('request');
    var Q = require('q');
    
    exports.hook_data = function () {
       var url, header;
       Q()
       .then(function(){
         connection.transaction.add_body_filter('', function(content_type, encoding, body_buffer) {
    
          header = connection.transaction.header.get("header");
    
          if (header == null || header == undefined || header == ''){return body_buffer;}
          url = 'https://server.com/api?header=' + header ;
    
       })
       .then(function(){
    
           request.get({ uri: url }, function (err, resp, body) {
    
               var resultFromServer = JSON.parse(body);
    
               return ChangeBuffer(content_type,encoding,body_buffer,resultFromServer);
           });
    
       })
    
    }
    

In async scenarios, using q for callbacks is more better than other ways I think.

0
Marcel Djaman On

JavaScript is asynchronous in nature. Asynchronous is a programming pattern which provides the feature of non-blocking code i.e do not stop or do not depend on another function / process to execute a particular line of code. Ref: Codementor Article

From Wikipedia

Node.js provides an event-driven architecture and a non-blocking I/O API designed to optimize an application's throughput and scalability for real-time web applications

What can you do?

  1. Use a sleep/wait ie setTimeout(Not recommended)
  2. Use some async lib like https://github.com/caolan/async (Recommended)
  3. Use some promise lib like Q