Promises in complex functions - node.js

I'm wondering what the best way to handle errors in long functions with promises are?
My function:
module.exports = function(data) {
var deferred = Q.defer();
var config = {};
fs.readdir(path, function(err, files) {
if (err) {
deferred.reject(new Error(err));
}
var infoPath = false;
files.forEach(function(filename) {
if (filename.indexOf('.info') !== -1) {
infoPath = path + '/' + filename;
config['moduleName'] = filename.replace('.info', '');
}
});
if (!infoPath) {
deferred.reject(new Error('Did not find .info-file'));
}
if (files.indexOf(config['moduleName'] + '.module') > -1) {
config.type = 'Modules';
}
else {
deferred.reject(new Error('Unknown project type'));
}
// Parse the info file.
fs.readFile(infoPath, function (err, content) {
if (err) {
deferred.reject(new Error(err));
}
data.config = config;
data.infoContent = content.toString();
deferred.resolve(data);
});
});
return deferred.promise;
};
As far as I understand it this is the way to use Q.defer. But if a error is thrown, I don't want/need it to try the rest of function. Am I missing something or are there a better way to do this?

Rejecting a promise doesn't miraculously stop the function from executing the rest of its code. So after rejecting, you should return from the function:
if (err) {
deferred.reject(new Error(err));
return;
}
Or shorter:
if (err) {
return deferred.reject(new Error(err));
}

Related

Do something async with underscore map

function addSomething(data) {
var defer = q.defer();
data = _.map(data, function(item) {
item['something'] = callSomethingAsync();
return item;
});
return defer.promise;
}
How can I handle this problem. The only way I found is using Async.js.
But maybe there is a better way using $q?
EDIT:
function getScopes(item) {
var defer = q.defer();
var query = "SELECT somevalue FROM Something WHERE ID = '" + item.id + "'";
mysql.query(query, function(err, data) {
if (err) {
defer.reject(err);
} else {
item[newkey] = data
defer.resolve(item);
}
});
defer.resolve(data)
return defer.promise;
}
//add necessary scopes to the audit
function addScopes(data) {
var promises = _.map(data, function(item) {
return getScopes(item);
});
return Promise.all(promises);
}
How I can prevent using defer in the getScopes function?
Edit 2:
var query = "SELECT * FROM tiscope";
Q.nfcall(mysql.query, query).then(function(data) {
console.log(data);
});
there is nothing returned.
Here is how I use mysql:
var sql = require('mysql');
var connection = sql.createConnection({
host : 'xxx',
user : 'xxx',
password : 'xxx',
database : 'xxx'
});
connection.connect(function(err) {
if (err) {
console.error('error connecting: ' + err.stack);
} else {
console.log('mysql connection established');
}
});
module.exports = connection;
Maybe there is the mistake.
A lot of promise libraries provide a map function. Seems Q does not. No matter the the same can be accomplished with vanilla promises (and Q) anyway using the all function.
First things first. Avoid defer. It makes code more difficult to reason and maintain. There are only a few rare cases when defer is needed. The rest of the time a normal promise constructor/helper functions will work better.
Normal Promises Example
function addSomething() {
var promises = _.map(data, function(item) {
return callSomethingAsync(item);
});
return Promise.all(promises);
}
Q Promises Example
function addSomething() {
var promises = _.map(data, function(item) {
return callSomethingAsync(item);
});
return $q.all(promises);
}
Presumably callSomethingAsync returns a promise. If not use the promise constructor pattern:
function toPromise(asyncFn, args) {
return new Promise(function (resolve, reject) {
function callback(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
}
asyncFn(callback, args);
});
}
function addSomething() {
var promises = _.map(data, function(item) {
return toPromise(callSomethingAsync, item);
});
return Promise.all(promises);
}

Cant set headers after they are sent node.js

I am trying to combine multiple textfiles,convert them in a single zip file using zip archiver.
exports.downloadFilesInZip = function(req, res, next) {
var respObj = {};
var file_names = [];
var projectId = 111;
var file_ids = 11111;
console.log(projectId);
db.getConnection(function (err, connection) {
if (err) {
debug(err);
next(err);
}
else {
var updateQuery = "select data from file_data where file_id IN (?)";
console.log(updateQuery);
connection.query(updateQuery,[file_ids], function (err, results) {
console.log("inside" + updateQuery);
if (err) {
connection.release();
console.log("error" + JSON.stringify(err));
debug(err);
next(err);
}
else {
async.eachSeries(results,function(item,loopCallBack){
var text = "";
console.log("hllllllll");
console.log(item.data);
console.log(JSON.parse(item.data));
document_text = JSON.parse(item.data);
console.log("dssddssdsdsdsdsd"+document_text);
for(var j=0; j < document_text.length ;j++)
{
text += document_text[j]['text'];
}
//file_names.push(convertStringToTextFile(text));
convertStringToTextFile(text,function(err,file_name){
if(err){
console.log(err);
loopCallBack(err);
}
else {
file_names.push(file_name);
loopCallBack();
}
})
},function(err){
if(err){
console.log(err);
next(err);
}
else {
var updateQuery = "select name from project where id in (?)";
console.log(updateQuery);
connection.query(updateQuery,[projectId], function (err, results) {
console.log("inside" + updateQuery);
connection.release();
if (err) {
console.log("error" + JSON.stringify(err));
debug(err);
next(err);
}
else {
var fileName_link = JSON.stringify(results[0].name);
console.log("projectname"+fileName_link);
convertTextFilesToZip(file_names,fileName_link, function (err, filename) {
if (err) {
console.log(err);
next(err);
}
else {
console.log("filename link" + filename);
res.json({
status: 0,
file_link: filename
});
}
});
}
});
}
});
}
});
}
});
}
}
convertStringToTextFile = function(text,cb){
var json_filename = 'tmp/file_'+uuid.v4().replace('-','')+'.txt';
fs.writeFile(json_filename, text , function (err) {
if (err) {
debug(err);
cb(err);
}
else{
cb(null,json_filename);
}
});
};
convertTextFilesToZip = function(textFiles,file_link,cb){
console.log("textfiles"+textFiles);
var filename = 'reports/'+JSON.parse(file_link)+'_extractedText.zip';
var output = fs.createWriteStream(filename);
output.on('close', function() {
console.log(zipArchive.pointer() + ' total bytes');
console.log('archiver has been finalized and the output file descriptor has closed.');
});
zipArchive.on('error', function(err) {
cb(err);
});
zipArchive.pipe(output);
zipArchive.bulk([
{ expand: true, src: textFiles }
]);
zipArchive.finalize();
cb(null,filename);
}
It works okay the first time and after that it throws this error.I have checked other posts in which res is returned twice but i couldn't find it.It says that can't set headers after they are sent.I think the problem is in the convertTextFilesToZip function but i cant seem to pinpoint the exact location which is generating the error.ANy help is appreciated.
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:350:11)
at ServerResponse.header (/Users/zeeshandar/Desktop/Agreements_info/agreements_info/node_modules/express/lib/response.js:700:10)
at ServerResponse.send (/Users/zeeshandar/Desktop/Agreements_info/agreements_info/node_modules/express/lib/response.js:154:12)
at fn (/Users/zeeshandar/Desktop/Agreements_info/agreements_info/node_modules/express/lib/response.js:934:10)
at View.exports.renderFile [as engine] (/Users/zeeshandar/Desktop/Agreements_info/agreements_info/node_modules/jade/lib/index.js:374:12)
at View.render (/Users/zeeshandar/Desktop/Agreements_info/agreements_info/node_modules/express/lib/view.js:93:8)
at EventEmitter.app.render (/Users/zeeshandar/Desktop/Agreements_info/agreements_info/node_modules/express/lib/application.js:566:10)
at ServerResponse.res.render (/Users/zeeshandar/Desktop/Agreements_info/agreements_info/node_modules/express/lib/response.js:938:7)
at /Users/zeeshandar/Desktop/Agreements_info/agreements_info/app.js:207:13
at Layer.handle_error (/Users/zeeshandar/Desktop/Agreements_info/agreements_info/node_modules/express/li b/router/layer.js:58:5)
Making my comment into an answer since it appears to have led to the solution.
The variable zipArchive is not initialized in convertTextFilesToZip() therefore you are reusing that variable from one function call to the next and that seems unlikely to be the right implementation.
Also, I would expect your method calls to zipArchive to be asynchronous and it doesn't look like your are coding for that since the callback is called before you have any sort of completion notification.

Node js nested async call

I want to do async call from my getData function to getImage function but i am unable to get return data from getImage().Since the getData() does't wait for the completion of getImage(),as getImage() has further async db calls and therefore getData() always returns undefined.
What is the best way to do this instead doing nested callbacks?
var getData = function(id){
async.series([
function(callback){
var res = getImages(id);
callback(null, res);
}
],
// optional callback
function(err, results){
if (err) {
console.log("ERROR : " + err);
}else
{
console.log("Result: "+results);
}
});
}
var getImages = function(id){
async.series([
function(callback){
Image.find({id: id }).exec(
function(err, image) {
if (err) {
console.log(err);
callback(err, 0);
}else
{ console.log("Count: "+ image.length);
callback(null, image);
}
});
}
],
// optional callback
function(err, results){
if (err) {
console.log("ERROR : " + err);
}else
{
return results;
}
});
}
getData(1);
As you said you need to wait for getImages() to return, and you do that using promises.
Use any promise library, like q for instance:
var q = require('q')
...
var getImages = function(id){
var deferred = q.defer();
...
//do async logic that that evaluates some res obj you wish to return
db.find(..., function() {
deferred.resolve(res);
}
return deferred.promise;
}
Then, from getData(), you call it in the following matter:
getImages(id).then(
function(res) {
callback(null, res);
},
function(err) {
console.log("error:" + err);
}
);
As you are already using async - just use the waterfall functionality: https://github.com/caolan/async#waterfalltasks-callback
This way you will be able to run functions one after another and wait for the previous to finish, while still getting it's return value.

execute promises recursively nodejs

the following function creates new folder on my server via xmlrpc
var createFolder = function(folder_name) {
var defer = Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) {
if (err.responseString && err.responseString.match('already exist')) {
//call the same function recursively with folder_name+Math.round(Math.random()*100)
} else {
defer.reject(err);
}
} else {
defer.resolve(folder_name);
}
});
return defer.promise;
}
The functions creates a new folder successfully
However, if folder already exists i want to fire this function again recursively with new folder name and then return it in promise so that whenever this function is called it'll return the folder name doesn't matter how many times it was executed
something like
createFolder('directory').then(function(resp){
console.log(resp);// may return directory || directory1 .... etc
});
**EDIT **
so i manged to achieve this by passing the defer object
let me know if there are more elegant ways of achieving this
var createFolder = function(folder_name,defer) {
defer =defer || Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) {
if (err.responseString && err.responseString.match('already exist')) {
return createFolder(folder_name+Math.round(Math.random()*100,defer)
} else {
defer.reject(err);
}
} else {
defer.resolve(folder_name);
}
});
return defer.promise;
}
Never do any logic in plain (non-promise) callbacks. Promisify at the lowest level:
var defer = Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) defer.reject(err);
else defer.resolve(folder_name);
});
return defer.promise;
Or much simpler with Q.ninvoke:
return Q.ninvoke(client, 'methodCall', 'create_folder', [sessionID, folder_name]);
Now we can start implementing our recursion. It's quite simple with a then callback, from which you can return another promise. In your case:
function createFolder(folder_name) {
return Q.ninvoke(client, 'methodCall', 'create_folder', [sessionID, folder_name])
.catch(function(err) {
if (err.responseString && err.responseString.match('already exist')) {
return createFolder(folder_name+Math.floor(Math.random()*100));
} else {
throw err;
}
});
}
Here is a bad simple way of solving your problem:
var createFolder = function(folder_name) {
var defer = Q.defer();
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) {
if (err.responseString && err.responseString.match('already exist')) {
//call the same function recursively with folder_name+Math.round(Math.random()*100)
defer.resolve(createFolder(folder_name+Math.round(Math.random()*100)));
} else {
defer.reject(err);
}
} else {
defer.resolve(folder_name);
}
});
return defer.promise;
}
However, defer is considered bad practice. Here is a very nice article about promises.
You should favor something like:
var createFolder = function(folder_name) {
return Q.Promise(function(resolve, reject){
client.methodCall('create_folder', [sessionID, folder_name], function(err, resp) {
if (err) {
if (err.responseString && err.responseString.match('already exist')) {
//call the same function recursively with folder_name+Math.round(Math.random()*100)
resolve(createFolder(folder_name+Math.round(Math.random()*100)));
} else {
reject(err);
}
} else {
resolve(folder_name);
}
});
});
}
EDIT: as noted by #Bergi, this is still not right and hard to debug. Any potential errors thrown from the callback of methodCall won't actually reject the promise and will most likely be swallowed (even though this callback seems very little error-prone, it might evolve). Please refer to his answer for a better way of doing this.
Also, see the official Q doc here.

Converting async code to q/promise code in nodejs

I've read through a lot of different articles on promisejs but can't seem to get it to work for my code. I have async code that works and does what I need but it's very long and doesn't look as clean as it could with promise.
Here's the two links I've been really looking into: http://jabberwocky.eu/2013/02/15/promises-in-javascript-with-q/ and https://spring.io/understanding/javascript-promises.
mainCode.js
accountModel.findOne({username: body.username}, function(err, usernameFound) {
console.log("here");
if (err) {
console.log(err);
} else {
console.log("here1");
anotherClass.duplicateUsername(usernameFound, function(err, noerr) {
if (err) {
console.log("error");
res.status(409).send("username");
} else {
console.log("here2");
accountModel.findOne({email: body.email}, function(err, emailFound) {
if (err) {
console.log("error2");
} else {
console.log("here3");
console.log(emailFound);
}
});
}
});
}
});
// anotherclass.duplicateUsername
anotherClass.prototype.duplicateUsername = function(usernameFound, callback) {
if (usernameFound) {
callback(usernameFound);
} else {
callback();
}
}
current promise code (in mainCode.js):
var promise = userModel.findOne({
username: body.username
}).exec();
promise.then(function(usernameFound) {
console.log("usernameFound")
return userCheck.duplicateUsername(usernameFound);
}).then(function(usernameFound) {
console.log("NOERR:" + usernameFound + ":NOERR");
console.log("noerror");
return;
}, function(error) {
console.log(err);
console.log("error");
res.sendStatus(409);
return;
});
When I run my promise code, it goes to duplicateUsername, does callback() but then doesn't print anything in the promise code.
duplicationUsername needs to return a promise, otherwise the promise chaining will get the value returned from calling callback (which would be undefined).
Something like this should work:
anotherClass.prototype.duplicateUsername = function(usernameFound) {
var deferred = Q.defer();
if (usernameFound) {
deferred.resolve(usernameFound);
} else {
deferred.reject();
}
return deferred.promise;
}
So it seems like I needed to "promisify" my own functions before I could use them.
Here's how I did it with Q:
var Q = require('q');
anotherClass.prototype.duplicateUsername = function(username, callback) {
return new Promise(function(resolve, reject) {
var deferred = Q.defer();
if (usernameFound) {
deferred.reject("error);
} else {
deferred.resolve("no err: duplicate username");
}
deferred.promise.nodeify(callback);
return deferred.promise;
});
}
Here's how to do it with Bluebird:
userCheck.prototype.duplicateUsername = function(usernameFound) {
return new Promise(function(resolve, reject) {
if (usernameFound) {
reject("error");
} else {
resolve();
}
});
}
Then in my mainClass, I just used them by calling the methods and then .then(//blah)

Resources