Sam3000 Sam3000 - 3 months ago 12
Ajax Question

Express Routing and Asynchronous Code - How do I send the result of Asynchronous Code?

A simple outline of what I'm trying to achieve:


  1. An AJAX call is made to a specific URL on the server

  2. Express Routes (in "app.js") then run Node.js code within "Send.js"

  3. The output of "Send.js" is used as a response to the AJAX request with the "res.send" method.






My problem is that "Send.js" makes use of asynchronous methods and so the "response" variable I am trying to return in "res.send(response)" is not defined when the response is sent, as the "sendOn" function in "Send.js" completes before the asynchronous code does, naturally.

I know that using a callback is likely to be the solution here, so that "res.send(response)" is not called until response is defined, I don't know how to implement that given what I have across two separate files here:

app.js (simplified) :

var send = require("./Files/Other/Send.js");


app.post('/Lamp-On', function (req, res)
{
var response = send.sendOn();
res.send(response);
});


Send.js (simplified) :

client.open and client.sendEvent are both asynchronous methods that accept a callback as a final argument, and are part of an external SDK.

var sendMessage = function ()
{
var data = "on";
var message = new Message(data);
message.properties.add('Turn On');
console.log('Sending message to turn the lamp: ' + message.getData());
client.sendEvent(message, printResultFor('Message'));
};

var connectCallback = function () {
if (err) {
console.error('Could not connect: ' + err.message);
}
else {

console.log('Client connected');

client.on('message', function (msg) {
console.log('Id: ' + msg.messageId + ' Body: ' + msg.data);
client.complete(msg, printResultFor('completed'));
// reject and abandon follow the same pattern.
// /!\ reject and abandon are not available with MQTT
});

client.on('error', function (err) {
console.error(err.message);
});

client.on('disconnect', function () {
clearInterval(sendInterval);
client.removeAllListeners();
client.connect(connectCallback);
});

// Now call the sendMessage function.
sendMessage();
}
};

var sendOn = function () {
client.open(connectCallback);
return
};

// Helper function to print results in the console
function printResultFor(op) {
return function printResult(err, res) {
if (err !== null) {
console.log(op + ' error: ' + err.toString());
console.log(err);
}
if (res) {
console.log(op + ' status: ' + res.constructor.name);
};
};
}

exports.sendOn = sendOn;


I just don't how to deal with so many asynchronous callbacks!

Final Note: I am neither asking nor expecting anyone to just do this for me, but I do hope you can point me in the right direction, thanks in advance.

Answer

The ONLY way to return a value is via callback. This is true even if you use a promise.

Callbacks is how you return a value. So since sendOn() needs to return a value it needs to accept a callback (alternatively return a promise and the promise will accept a callback).

Basically app.js would look like this:

app.post('/Lamp-On', function (req, res)
{
    send.sendOn(function(err,response){
        res.send(response);
    });
});

One of the things you need to get used to is to stop thinking of functions as subroutines. Instead think of functions as control structures like for or while or if. You're used to writing code like:

if (something) {
    something_else();
}

Conceptually you then shouldn't have much trouble with:

doIf(something,function(){
    something_else();
});

Now comes the interesting part. How to make sendOn() accept a callback? Just pass that callback all the way down to the function that "returns" the result. Basically, the process is inverted from procedural programming, instead of returning a value all the way to outer code you pass code all the way to the value:

var sendOn = function (callback) {
    client.open(function(){
        connectCallback(callback); // pass the CODE down to the value
    });
};

var connectCallback = function (callback) {
    if (err) {
        callback(err);
    }
    else {
        client.on('message', function (msg) {
            console.log('Id: ' + msg.messageId + ' Body: ' + msg.data);

            callback(null, msg.data); // Now pass the value to the CODE
        });

        sendMessage();
    }
};

I'm assuming that msg.data is the result. If not change it accordingly. Make sure you pass the result to the callback you've passed all the way down from sendOn().