I have a simple function tokenExists
which checks to see whether a token is already part of a Redis set
. I know that resp
is coming back as undefined
because that final line of code is running before the function is done making the request and getting the response from Redis
. I have read many SO articles on handling the async
nature of Redis
with Node
but almost all of those had to do either with multi-line commands or Express
and route specific issues. I am wondering how to make this code synchronous.
function tokenExists(token, callback) {
db.sismember("tokens", token, function(err,res) {
if (err) throw err;
callback(null, parseInt(res, 10) === 1);
});
}
function generateToken(){
try {
var token = urlSafeBase64(crypto.randomBytes(23));
} catch (ex) {
console.log(ex)
}
tokenExists(token, function(err,res){
if (err) throw err;
res ? token : generateToken();
})
}
^^ this method is returning 'undefined'
You can't.
The database call is async so you will have to use a callback for the tokenExists function.
function tokenExists(token, callback) {
db.sismember("tokens", token, function(err,res) {
if (err) throw err;
callback(null, parseInt(res, 10) === 1);
});
}
I'm not sure in Javascript, and these are kind of pseudo codes:
You may want to use generateToken
in a kind of synchronous way, like this:
void foo() {
var token = generateToken();
print(token);
use(token);
}
But you may not be able to use it in the kind of way, as Barış Uşaklı already said, because node.js uses only one thread and you may not be allowed to block the thread. So, if you want to use it in an asynchronous way:
void foo() {
generateToken(function(token) {
print(token);
use(token);
});
}
And you should change your generateToken
:
function generateToken(callback){ // this line is changed.
try {
var token = urlSafeBase64(crypto.randomBytes(23));
} catch (ex) {
console.log(ex)
}
tokenExists(token, function(err,res){
if (err) throw err;
callback(res ? token : generateToken(callback)); // this line is changed.
})
With asynchronous code, you really have to embrace the continuation pattern completely. Since you've made one async call with your redis call, you have to keep going all the way to the original caller.
Here's your code modified a bit:
function tokenExists(token, callback) {
db.sismember("tokens", token, function(err,res) {
if (err) throw err;
callback(null, parseInt(res, 10) === 1);
});
}
//add an additional callack to generateToken
function generateToken(callback){
try {
var token = urlSafeBase64(crypto.randomBytes(23));
} catch (ex) {
console.log(ex)
}
//once this async call is made, you're off the main event loop.
tokenExists(token, function(err,res){
//standard node convention passes an error function as the first paramter
//you can log it if you want,but let the callback know there was an error
if (err) return callback(err,null);
if(res){
return callback(null,res);
}
generateToken(callback);
})
}
You must provide the callback from the executing code and handle the results there. Here's an example what will send the token on express route callback. If you're doing more complicated things with the token, like saving a record (asynchrounously of course), then you're going to have to manage multiple callbacks within the originating method call. Libraries like async or promise libraries like Q or Bluebird can help with that. You can also look at callbackhell.com to see how to do it with plain javascript using named methods and modules.
//calling your generateToken
app.get('/token',function(req, res){
generateToken(function(err, token){
if(err) return res.send(500,err);
res.end(token);
})
});