I'm in deep trouble trying to understand how to make my code asynchronous in Node.js land. Please see my code below, in two varieties.
Ok so here's my first try - I have three functions here. A processing function (iim), a file copy function (fse.copy), and an archive function (da).
I need da to happen after iim, and iim to happen after fse.copy.
This first approach results in archive happening, but it's empty because iim never appears to happen.
da(randomString, function(err) {
if (err) {
log.error(err);
} else {
fse.copy(temp_path, new_location + file_name, function(err) {
if (err) {
log.error(err);
} else {
log.info("File saved to " + new_location + file_name);
var sourceImage = new_location + file_name;
log.debug(sourceImage);
log.debug(randomString);
iim(sourceImage, randomString, function(err) {
if (err) {
log.error(err);
}
});
}
});
}
});
The next block is an alternate approach which results in the da happening before iim is finished.
fse.copy(temp_path, new_location + file_name, function(err) {
if (err) {
log.error(err);
} else {
log.info("File saved to " + new_location + file_name);
var sourceImage = new_location + file_name;
log.debug(sourceImage);
log.debug(randomString);
iim(sourceImage, randomString, function(err) {
if (err) {
log.error(err);
}
});
da(randomString, function(err) {
if (err) {
log.error(err);
}
});
}
});
Here's what I'd recommend -- in your question you say you need to essentially run three functions in series -- correct? Run function A, then function B, and lastly, run function C.
The simplest way to do this is using the asyncjs library.
Here's an example:
var async = require('async');
async.series([
function a(cb) {
// do stuff
cb();
},
function b(cb) {
// do stuff
cb();
},
function c(cb) {
// do stuff
cb();
},
], function() {
// this will run once all three functions above have finished
});
Now, let's say that each of those functions needs to return data to the next function. SO imagine that function B needs input from function A to run. How do you accomplish that? Using async.waterfall!
var async = require('async');
async.waterfall([
function a(cb) {
// do stuff
cb(null, 'value');
},
function b(val, cb) {
// do stuff with val
cb(null, 'woot');
},
function c(val, cb) {
// do stuff with val
cb(null);
},
], function() {
// this will run once all three functions above have finished
});
Not bad right?
Hope this helps!
EDIT: Here's a code block showing your code above refactored using asyncjs:
async.waterfall([
function(cb) {
fse.copy(temp_path, new_location + file_name, function(err) {
if (err) {
log.error(err);
} else {
log.info("File saved to " + new_location + file_name);
var sourceImage = new_location + file_name;
log.debug(sourceImage);
log.debug(randomString);
}
console.log('Finished running fs.copy');
cb(null, sourceImage, randomString);
});
},
function(sourceImage, randomString, cb) {
iim(sourceImage, randomString, function(err) {
if (err) {
log.error(err);
}
console.log('Finished running iim');
cb(null, randomString);
});
},
function(randomString, cb) {
da(randomString, function(err) {
if (err) {
log.error(err);
}
console.log('Finished running da');
cb();
});
}
], function() {
console.log('All done!');
});
So you can either put da into the callback for iim (right now it's not) from your second example:
fse.copy(temp_path, new_location + file_name, function(err) {
if (err) {
log.error(err);
} else {
log.info("File saved to " + new_location + file_name);
var sourceImage = new_location + file_name;
log.debug(sourceImage);
log.debug(randomString);
iim(sourceImage, randomString, function(err) {
if (err) {
log.error(err);
return;
}
da(randomString, function(err) {
if (err) {
log.error(err);
}
});
});
}
});
That said, callback depth can be flattened with the use of a library like async (https://github.com/caolan/async)
Related
I'm using NodeJS and using the
tls.connect(port, host, options, callback)
To get my socket to write to. I pass it through a async.waterfall and the socket.writable property stays set to true and I can write to the socket throughout. However, if I try to use it with setTimeout, it ends up being closed. Is there a way to keep it open or is my syntax incorrect?
This is one of the calls in async waterfall:
function (sock, err, callback) {
console.log(sock.writable); // this is true
setTimeout(function(sock) {
console.log(sock.writable); // this is false but I'd like it to be true so I can use it for more logic
}, 3000, sock);
}
I've also tried
function (sock, err, callback) {
console.log(sock.writable); // this is true
var sockz = sock;
setTimeout(function() {
console.log(sockz.writable); // this is false but I'd like it to be true so I can use it for more logic
}, 3000);
}
and this
function (sock, err, callback) {
console.log(sock.writable); // this is true
setTimeout(function(sock) {
console.log(sock.writable); // this is false but I'd like it to be true so I can use it for more logic
}.bind(this, sock), 3000);
// callback(null, sock);
}], function (err, sock) {
console.log(sock.writable);
}
But all of them print false. Is my syntax incorrect or does the socket automatically close in this scenario where I'm trying to get it to wait?
Edit
Full async.waterfall:
async.waterfall([
// insert record into db
function (acb) {
dbConn.query()
.insert(tableName, ["HOST", "SERVER_NAME", "DATE_CREATED"], [hostname, "test", currentTime])
.execute(function (err, result) {
if (err) {
componentStatus.database = false;
// pass the error down to run all the functions
acb(null, sockz, componentStatus, err);
} else {
componentStatus.resultId = result.id;
acb(null, sockz, componentStatus, null);
}
});
},
// read record back
function (sockz, componentStatus, err, acb) {
if (componentStatus.resultId < 0) {
acb(null, sockz, componentStatus, err);
} else {
dbConn.query().select("ID").from(tableName).where("ID=?", [componentStatus.resultId]).execute(function (err, rows, cols) {
if (err) {
componentStatus.database = false;
acb(null, sockz, componentStatus, err);
} else {
acb(null, sockz, componentStatus, err);
}
});
}
},
// send message to rabbit
function (sockz, componentStatus, err, acb) {
sendStatusMessage(currentTime, componentStatus.resultId, function (err1, result) {
if (err1) {
componentStatus.messaging = false;
var error = err + ", " + err1;
acb(null, sockz, componentStatus, error);
} else {
console.log("rabbit timeout");
console.log(sockz.writable);
setTimeout(function(sockz) {
console.log("In timeout");
console.log(sockz.writable);
}.bind(this, sockz), 3000); // wait for 3s to check record was consumed
}
});
}
], function (err, sockz, componentStatus, actualError) {
console.log(sockz.writable);
});
}
});
};
exports.sendStatusMessage = function(dateCreated, id, callback) {
var notification = { "dateCreated": dateCreated, "id": id };
_sendMsg("test.queue", notification, {"contentType": "application/json"}, function (err, result) {
if (err) {
callback(err, null);
} else {
callback(null, result);
}
});
}
I am writing this code as a project for a customer
and when i go to a show route i got this 500 internal server error
http.get('/files/:id', function(req, res) {
var vid;
var pap;
Videos.find({}, function(err, videos) {
if (err) {
console.log(err);
} else {
vid = videos;
}
});
Papers.find({}, function(err, file) {
if (err) {
console.log(err);
} else {
pap = file;
}
});
Material.findById(req.params.id, function(err, found) {
if (err) {
console.log(err);
} else {
res.render('files', {
file: pap,
video: vid,
current: found
});
}
});
});
this is my show route code.
Note : if i reload the page the error is gone and the page open.
The reason is you need to wait for all the database queries to finish before rendering. In your code, it is possible for the page to render before the other two queries have completed and returned their data. The good news is that Mongoose supports Promises for asynchronous functions.
http.get('/files/:id', function(req, res) {
Promise.all([
Videos.find({}).exec(),
Papers.find({}).exec(),
Material.findById(req.params.id).exec()
]).then( ([video, paper, material]) => {
res.render('files', {
file: paper,
video: video,
current: material
});
}).catch( error => console.log(error) );
});
The functions you're using with Mongoose are asynchronous in nature; the variables vid and pap are not initialized when you run res.render. When you attempt to use those variables in your frontend (template like Jade, Handlebars EJS, I don't know what you're using), they are undefined, and subsequently cause the 500 error. You'll need to run the functions such that the results of all Mongoose queries are available to res.render when it runs; either using an async NodeJS library, or calling each function within one another and then calling res.render at the end.
Solution 1: Using async Node module
var async = require('async');
async.parallel([
// Each function in this array will execute in parallel
// The callback function is executed once all functions in the array complete
function (cb) {
Videos.find({}, function(err, videos) {
if (err) {
return cb(err);
} else {
return cb(null, videos);
}
});
},
function (cb) {
Papers.find({}, function(err, papers) {
if (err) {
return cb(err);
} else {
return cb(null, papers);
}
});
},
function (cb) {
Material.findById(req.params.id, function(err, found) {
if (err) {
return cb(err);
} else {
return cb(null, found);
}
});
}
], function (err, results) {
if (err) {
// If any function returns an error
// (first argument), it will be here
console.log(err);
}
else {
// Even though the functions complete asynchronously,
// the order in which they are declared in the array
// will correspond to the position in the array
// if it returns anything as a second argument.
var videos = results[0];
var files = results[1];
var found = results[2];
res.render('files', {
file: files,
video: videos,
current: found
});
}
});
Solution 2: Nested Callbacks
Videos.find({}, function(err, videos) {
var vid = videos;
if (err) {
console.log(err);
} else {
Papers.find({}, function(err, file) {
var pap = file;
if (err) {
console.log(err);
} else {
Material.findById(req.params.id, function(err, found) {
if (err) {
console.log(err);
} else {
res.render('files', {
file: pap,
video: vid,
current: found
});
}
});
}
});
}
});
Okay, so heres the full source of my function. All I want that the part that is surrounded by "////////////" would repeat. New function works too. I could have them both, very confused once I tried to pull the highlighted function into a new one and got loads of errors.
function reWebLogOn(steam, callback) {
steam.webLogOn(function(newCookie){
helper.msg('webLogOn ok');
cookies = newCookie;
offers.setup({
sessionID: currentSessionId,
webCookie: newCookie
}, function(){
if (typeof callback == "function") {
callback();
}
});
var steamcommunityMobileConfirmations = new SteamcommunityMobileConfirmations(
{
steamid: config.steamid,
identity_secret: config.identitySecret,
device_id: device_id,
webCookie: newCookie,
});
///////////////////////////////////////////////////////////////////////////////////////////////////////////
steamcommunityMobileConfirmations.FetchConfirmations((function (err, confirmations)
{
if (err)
{
console.log(err);
return;
}
console.log('steamcommunityMobileConfirmations.FetchConfirmations received ' + confirmations.length + ' confirmations');
if ( ! confirmations.length)
{
return;
}
steamcommunityMobileConfirmations.AcceptConfirmation(confirmations[0], (function (err, result)
{
if (err)
{
console.log(err);
return;
}
console.log('steamcommunityMobileConfirmations.AcceptConfirmation result: ' + result);
}).bind(this));
}).bind(this));
///////////////////////////////////////////////////////////////////////////////////////////////////////////
});
}
use timer's, an Interval would be handy
setInterval(function() {
//.. part that should be repleated
}, 30*1000);
window.setInterval() is your friend.
it executes a function at the provided interval of time.
for example, setInterval(()=>console.log("foo"),100) will log "foo" in the console every 100ms.
function reWebLogOn(steam, callback) {
steam.webLogOn(function(newCookie){
helper.msg('webLogOn ok');
cookies = newCookie;
offers.setup({
sessionID: currentSessionId,
webCookie: newCookie
}, function(){
if (typeof callback == "function") {
callback();
}
});
var steamcommunityMobileConfirmations = new SteamcommunityMobileConfirmations(
{
steamid: config.steamid,
identity_secret: config.identitySecret,
device_id: device_id,
webCookie: newCookie,
});
///////////////////////////////////////////////////////////////////////////////////////////////////////////
setInterval((function(){
steamcommunityMobileConfirmations.FetchConfirmations((function (err, confirmations){
if (err)
{
console.log(err);
return;
}
console.log('steamcommunityMobileConfirmations.FetchConfirmations received ' + confirmations.length + ' confirmations');
if ( ! confirmations.length)
{
return;
}
steamcommunityMobileConfirmations.AcceptConfirmation(confirmations[0], (function (err, result)
{
if (err)
{
console.log(err);
return;
}
console.log('steamcommunityMobileConfirmations.AcceptConfirmation result: ' + result);
}).bind(this));
}).bind(this));
}).bind(this),30000)
Put your code inside setInterval(function(){},1000) timer or do some recursive calls.
I use the following code and it seems that the callback (Which start with Im HERE) is not called, any idea why?
console.log("im starting");
process.start(function() {
//this line doesnt called
console.log("im HERE");
server.listen(app.get('port'), function(err) {
if (err) {
console.error(err);
} else {
console.log(' listen to: ' + app.get('port'));
}
});
});
the method start are called and finish ...any idea what it can be ?
before ive added the process.start the code look like following:
And this works OK, now I need to add this process.start and when it finish to do the server.listen
module.exports = (function() {
server.listen(app.get('port'), function(err) {
if (err) {
console.error(err);
} else {
console.log('listen ' + app.get('port'));
}
});
}());
UPDATE
This is the code of process start
exports.start = function () {
Validator.validateJson(function (err) {
console.log(err);
process.exit(1);
});
plugin.parse().then(function (conf) {
require.cache.pe.configObj = conf;
}, function (err) {
console.log(err);
});
envHandler.eventE.on('AppP', function () {
console.log('User port ' + require.cache.per);
});
var run= function () {
return Promise.all([
childPro.create(path.join(value)),
childPro.findAndUpdateUser()
]).spread(function (cmd,updatedAppEnv) {
return Promise.all([childProc.executeChildProcess('exec', cmd, updatedAppEnv), Promise.delay(50).then(function (results) {
return inter.ProcessRun(val);
})]);
})
}();
}
I use promise lib like bluebird if its matter in this case
It's a bit unclear where you want to call the callback. In short, change the start function to accept a callback parameter and call callback() when you are done (or pass it at end as argument to then).
exports.start = function (callback) {
Validator.validateJson(function (err) {
console.log(err);
process.exit(1);
});
plugin.parse().then(function (configObj) {
if (typeof require.cache.persist === 'undefined') {
require.cache.persist = {};
}
require.cache.persist.configObj = configObj;
}, function (err) {
console.log(err);
});
envHandler.eventEmitterIns.on('AppPortDef', function () {
console.log('User port ' + require.cache.persist.port);
});
var run= function () {
return Promise.all([
childPro.create(path.join(value)),
childPro.findAndUpdateUser()
]).spread(function (cmd,updatedAppEnv) {
return Promise.all([childProc.executeChildProcess('exec', cmd, updatedAppEnv), Promise.delay(50).then(function (results) {
return inter.ProcessRun(val);
})]);
})
}();
run.then(callback);
}
To catch errors I have written if-else blocks in every function which looks bad. Please suggest a better way to handle errors in async node
async.waterfall([
function(callback){
fnOne.GetOne(req, res,function(err,result) {
if(err){
console.error("Controller : fnOne",err);
callback(err,null);
}
else{
var fnOne = result;
callback(null, fnOne);
}
})
},
function(fnOne, callback){
fnTwo.two(fnOne,function(err,result) {
if(err) {
console.error(err);
callback(err,null);
}
else{
callback(null, context);
}
})
}
], function (err, result) {
if(err){
console.error("Controller waterfall Error" , err);
res.send("Error in serving request.");
}
});
You can pass the error to async and catch it in the callback
async.waterfall([
function (callback) {
fnOne.GetOne(req, res, callback); // err and result is passed in callback
}, // as it's "function(err, result)"
function (fnOne, callback) { // the same as the arguments for the
fnTwo.two(fnOne, callback); // callback function
}
], function (err, result) {
if (err) {
console.error("Error :", err);
res.send("Error in serving request.");
}else{
res.end("A-OK");
}
});
You do too much stuff
Waterfall already have an internal error management.
callback(err, [results]) - An optional callback to run once all the
functions have completed. This will be passed the results of the last
task's callback.
Try this
async.waterfall([
function(callback){
fnOne.GetOne(req,res, callback)
},
function(fnOne, callback){
fnTwo.two(fnOne,callback) {
}
], function (err, result) {
if(err){
console.error("Controller waterfall Error" , err);
res.send("Error in serving request.");
}
});
async.each(files, (file, callback) => {
// Create a new blob in the bucket and upload the file data.
const blob = bucket.file(file.file.originalname);
const blobStream = blob.createWriteStream();
blobStream.on('error', (err) => {
callback(err);
});
blobStream.on('finish', () => {
// The public URL can be used to directly access the file via HTTP.
Storage.bucket(BUCKET_NAME)
.file(blob.name)
.move(body.email + '_' + file.dir + '.' + blob.name.split('.').pop())
.then((e) => {
body[file.dir] = format(`https://storage.googleapis.com/${BUCKET_NAME}/${e[0].name}`)
callback();
})
.catch(err => {
console.error('ERROR: ', err);
});
});
blobStream.end(file.file.buffer);
}, (err) => {
if (err) {
console.error(err);
return res.status(422).send({error: true, data: {message: "An error occured. Please fill all fields and try again"}});
}
// save to db
});