Zaid Humayun Zaid Humayun - 3 months ago 8
Node.js Question

How to create asynchronous callbacks in node js?

var express = require('express');
var GoogleUrl = require('google-url');
var favicon = require('serve-favicon');
var mongo = require('mongodb').MongoClient;
var app = express();
var db;
var googleUrl = new GoogleUrl({key: 'AIzaSyB5i1sdnt6HwzrvsPTBu0FbPiUZrM_BCsk'});
var PORT = 8080;

mongo.connect('mongodb://localhost:27017/url-shortener', function(err, newDb){
if(err){
throw new Error('Database failed to connect');
} else{
console.log('Successfully connected to MongoDB on port 27017');
}
db=newDb;
db.createCollection('sites', {
autoIndexID: true
});
});

app.use(favicon(__dirname+'/public/favicon.ico'));

app.get('/new/*', function(req, res){
var doc;
console.log('This is the url: '+req.params[0]);
googleUrl.shorten(req.params[0], function(err, shortUrl){
if(err){
console.log(err);
}else{
console.log(shortUrl);
}
doc = check_db(req.params[0], shortUrl, db);
});


The below res.json statement runs and returns an undefined variable before the other functions have a chance of returning a value.

res.json(doc);
});


app.listen(process.env.PORT, function(){
console.log('Express listening on: '+PORT);
});

function check_db(longUrl, shortUrl, db){
db.collection('sites').findOne({
'longUrl': longUrl,
'shortUrl': shortUrl
}, function(err, doc){
if(err){
console.log(err);
}if(doc){


The res.json statement is executed before the below statements are executed and a value can be returned.

console.log('This site already exists on the database');
return doc;
}else{
save_db(longUrl, shortUrl, db);
}
});

}

function save_db(longUrl, shortUrl, db){
db.collection('sites').insert({
'longUrl': longUrl,
'shortUrl': shortUrl
}, function(err, doc){
if(err){
throw err
}else{
console.log(doc);
}
db.close();
});
}


In the above code, the res.json statement executes before the functions defined below the GET request have a chance to complete execution, with the result that res.json returns an undefined variable. I know that I have to implement asynchronous functionality into my app (potentially promises?), but I am at a complete loss as to how to do so!

Answer

A callback is just an argument, in a function call, so

googleUrl.shorten(req.params[0], function(err, shortUrl){
   if(err){
       console.log(err);
   }else{
       console.log(shortUrl);
   }
   doc = check_db(req.params[0], shortUrl, db);
});
res.json(doc);

behaves just like

foo(a, b);
bar();

Your call to #googleUrl.shorten() is immediately followed by your call to #res.json(), just like the call to #foo() is immediately followed by a call to #bar().

Your callback function:

function(err, shortUrl){
   if(err){
       console.log(err);
   }else{
       console.log(shortUrl);
   }
   doc = check_db(req.params[0], shortUrl, db);
}

Is executed asynchronously, meaning it does not interrupt regular control flow. When you need to postpone execution of a statement like #res.json(doc), you must make sure that it's execution is also taken out of regular control flow. To do that, you'll need to accept a callback argument in both #check_db() and #save_db(). The new function signatures will look like this:

function save_db(longUrl, shortUrl, db, callback)

and

function check_db(longUrl, shortUrl, db, callback)

Then pass a callback function, which accepts an argument 'doc', and executes res.json(doc), to your db functions. eg:

doc = check_db(req.params[0], shortUrl, db, function(doc){
  res.json(doc);
});

Note: You'll simply pass 'callback' to #save_db() from #check_db().

Finally, call the callback, rather than returning a value, once your asynchronous functions have completed execution. eg:

db.collection('sites').findOne({
    'longUrl': longUrl, 
    'shortUrl': shortUrl
}, function(err, doc){
    if(err){
        console.log(err);
    }if(doc){
        console.log('This site already exists on the database');
        callback(doc);
    }else{
        save_db(longUrl, shortUrl, db, callback);
    }
});

I've left the changes to #save_db() up to you. Good luck!

Comments