Running through multiple functions in nodejs / expressjs
Problem
How do you run through multiple functions in nodejs / expressjs? In php it's straightforward, call one Function after the other but this callback business in node is confusing, I keep getting errors that a variable isn't defined and such. Here is the basic idea of what I'm doing.
var express = require('express');
var request = require('request');
var app = express();
app.get('/user/:id', function(req, res) {
var id = req.params.id;
getInformation(id, function(info) {
res.send(info);
});
});
app.listen(3000);
getInformation(id, callback) {
var qty = makeExternalApiCall();
var color = secondFunction(id);
callback({quantity: qty, color: color});
}
makeExternalApiCall() {
request({uri: 'https://provider.com/api/stuff/'}, function(error, response, body) {
if (!error && response.statusCode == 200) {
return body.qty;
}
}
}
secondFunction(id) {
//look up color by id
var color = "blue";
return color;
}
Solution
Running through multiple functions is similar to as it would be in PHP, unless it involves asynchronous functions. An asynchronous callback is a function that can be called at any time, and will not work with the return
keyword. Take this callback for example:
var cb = function(arg) {
console.log(arg);
};
We can pass this callback function into another function, and have that function call cb()
from within:
function call(text, callback) {
callback(text);
};
var txt = 'a string';
call(txt, cb);
console.log('breakpoint');
The example above runs synchronously. Therefore the order of execution is:
call() -> cb()
console.log()
But if we delay the function or add a timer (process.nextTick
waits until the callstack of functions is empty, then executes what has been queued):
function call(text, callback) {
process.nextTick(function() {
callback(text);
});
};
And run it again, we get a different execution order because cb()
was queued to run after the callstack is empty (right after console.log()
runs, it's empty):
call()
console.log()
-> cb()
Most undefined variable errors are caused by accessing a variable before it's set. For example, take the asynchronous function foo()
.
var data;
foo(function(bar) {
data = bar;
});
console.log(data);
The callback function(bar) { ... });
may have been called after the console.log()
, which means console.log()
runs before data
is given a value.
As for your specific problem, the request module is asynchronous and uses a callback, so you can't use a return value from within the HTTP request. To get a resultant value from within a callback function, you need to pass it to another callback. Using the return
keyword will just stop the function's execution. So change this:
var options = {
uri: 'https://provider.com/api/stuff/'
};
function makeExternalApiCall() {
request(options, function(err, res, body) {
if (!err && res.statusCode == 200) {
return body.qty;
}
}
};
To use a callback:
function makeExternalApiCall(callback) {
request(options, function(err, res, body) {
if (!err && res.statusCode == 200) {
callback(null, body.qty);
}
}
};
The function would then be used like so:
makeExternalApiCall(function(err, qty) {
// here is qty
});
So your routing code might look like this, using nested callbacks:
function getInformation(id, callback) {
var color = secondFunction(id);
makeExternalApiCall(function(err, qty) {
callback({ quantity: qty, color: color });
});
};
app.get('/user/:id', function(req, res) {
var id = req.params.id;
getInformation(id, function(info) {
res.send(info);
});
});
Discussion
View additional discussion.