I'm learning node and would like to optimize the code I did. I tried using Async.parallel to perform the operations and when finished return a json.
I am new to js node and I'm trying to do with async.parallel but I returned [Function] in other code that I'm trying to understand it
getTabletIntRout: function(req, res) {
var reqMAC = req.param('id_tablet');
Tablet.findOne(reqMAC).populate('rout_tablet').exec(function(err, tablet) {
if (err) return next(err);
if (!tablet) return next();
var arrRoutes = tablet.rout_tablet;
if (arrRoutes.length > 0) {
var routesNotRemoved = [];
arrRoutes.forEach(function(route) {
if (route.removed == 'no') {
Rout.findOne(route.id)
.populate('rout_assigned') // Pin
.populate('in_rout') // Tablet
.populate('rout_description_assigned')
.exec(function(err, rout2) {
var arrRout = rout2.rout_assigned;
var routsNotRemoved = [];
if (arrRout.length > 0) {
arrRout.forEach(function(ruta) {
if (ruta.removed == 'no') {
routsNotRemoved.push(ruta);
}
});
}
var arrTablets = rout2.in_rout;
var tabletsNotRemoved = [];
if (arrTablets.length > 0) {
arrTablets.forEach(function(tab) {
if (tab.removed == 'no') {
tabletsNotRemoved.push(tab);
}
});
}
var arrDesc = rout2.rout_description_assigned;
var descripNotRemoved = [];
if (arrDesc.length > 0) {
arrDesc.forEach(function(desc) {
if (desc.removed == 'no') {
descripNotRemoved.push(desc);
}
});
}
rout2.rout_assigned = routsNotRemoved;
rout2.in_rout = tabletsNotRemoved;
rout2.rout_description_assigned = descripNotRemoved;
routesNotRemoved.push(rout2);
});
}
});
setTimeout(function() {
if (routesNotRemoved.length > 0) {
res.json({ info: routesNotRemoved });
} else {
return res.json({"error": "-1", "message": "Todas las rutas asociadas a esa tablet están eliminadas"});
}
}, 2000);
} else {
return res.json({"error": "-2", "message": "No existen rutas asociadas en esa tablet"});
}
}););});}},
I will try to provide some thoughts, hopefully some will make sense in your domain.
Extract a function to make people understand what you're doing in that big function
So instead of
Tablet.findOne(reqMAC).populate('rout_tablet').exec(function(err, tablet) { // ...
You would have
Tablet.findOne(reqMAC).populate('rout_tablet').exec(meaningfulFunctionName);
Don't repeat yourself
So your code becomes shorter and whenever the reader of your code finds a function name he / she already knows what is happening inside that
if (arrRout.length > 0) {
arrRout.forEach(function(ruta) {
if (ruta.removed == 'no') {
routsNotRemoved.push(ruta);
}
});
}
No need to check for empty arrRout as the argument function to arrRout.forEach will simply not run in the case of length being zero.
What you wrote is just a filter function, so why not using filter? Like so
arrRout.filter(function(ruta) {
return ruta.removed == 'no';
});
You can also reuse this, if you extract the anonymous function, for arrTablets and arrDesc.
On the argument: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Don't use a huge if else
Either check for the inverse or return a default or something that makes sense in your domain, but don't have that big chunk of logic, it makes it hard to reason about your code.
Extract more function so that it's easier to use async
You might want to have something like this
async.waterfall([
function(next) {
// here you can put
// Tablet.findOne(reqMAC).populate('rout_tablet').exec
// invoke next with err, tablet
},
function(tablet, next) {
async.each(arrRoutes, function(arrRoute, nextEach) {
// write your code logic here
});
}
], function() {
// decide what to invoke res.json with
});
Remember to extract functions after you're done putting the logic inside the async steps, I didn't do it so it is more clear where to put what.
I hope this makes sense, feel free to ask if you have any doubts.
Next time you post a question please make sure to properly indent it, don't just paste it here.
Related
I'm trying to prevent the user to save a piece if it doesn't achieve some requirements.
Currently I'm doing it like this:
self.beforeSave = function(req, piece, options, callback) {
let success = true;
let error = "";
if (Array.isArray(piece._subevents) && piece._subevents.length) {
success = self.checkDateAndTimeCompabilitiyWithChildren(piece);
}
if (!success) {
self.apos.notify(req, "Check the compatibility between parent event and subevents", { type: "error" });
error = "Subevents are not compatible with parent event";
}
callback(error);
};
This works but the problem is it shows 2 errors notifications (the default and my custom), 1 because of callback(error) and 1 because of apos.notify.
Any idea how to stop the item of being saved and only show my notification?
Thanks in advance.
UPDATE 1:
As Tom pointed out, my code looks like this now:
// lib/modules/events/public/js/editor-modal.js
apos.define('events-editor-modal', {
extend: 'apostrophe-pieces-editor-modal',
construct: function(self, options) {
self.getErrorMessage = function(err) {
if (err === 'incompatible') {
apos.notify('A message suitable for this case.', { type: 'error' });
} else {
apos.notify('A generic error message.', { type: 'error' });
}
};
}
});
// lib/modules/events/index.js
var superPushAssets = self.pushAssets;
self.pushAssets = function() {
superPushAssets();
self.pushAsset("script", "editor-modal", { when: "user" });
};
self.beforeSave = async function(req, piece, options, callback) {
return callback("incompatible")
};
For testing purposes I'm just returning the error in beforeSave. The problem is that an exception is being thrown in the browser console and the modal is not properly rendered again. Here's a screenshot about what I'm talking:
I'm trying to debug it and understand what's happening but no clue yet.
In your server-side code:
self.beforeSave = function(req, piece, options, callback) {
let success = true;
if (Array.isArray(piece._subevents) && piece._subevents.length) {
success = self.checkDateAndTimeCompabilitiyWithChildren(piece);
}
if (!success) {
return callback('incompatible');
}
return callback(null);
};
And on the browser side:
// in lib/modules/my-pieces-module/public/js/editor-modal.js
apos.define('my-pieces-module-editor-modal', {
extend: 'apostrophe-pieces-editor-modal',
construct: function(self, options) {
self.getErrorMessage = function(err) {
if (err === 'incompatible') {
return 'A message suitable for this case.';
} else {
return 'A generic error message.';
}
};
}
});
If the error reported by the callback is a string, it is passed to the browser. The browser can then recognize that case and handle it specially. 'my-pieces-module-editor-modal' should be substituted with the name of your pieces module followed by -editor-modal.
I am a php programmer recently I have startled using nodejs in-order to get a project done which is unfortunately may be fortunately using dynamo db as well so here I am coming out with a problem i know it might be basic might be as stupid as it is
The code which is excecuting is here
if (typeof(req.body.question) == 'object') {
quest = req.body.question;
}
if (typeof(req.body.question) == 'string') {
quest = req.body.question;
}else{
questionsq = {
'ConsistentRead': true,
TableName : 'testquestions',
KeyConditionExpression: "testid = :testid",
ExpressionAttributeValues: { ":testid":{'S':'f3b21bf0-d6b9-11e8-bdf1-f7fcc44e7f9c'} }
};
vulog.info('Question is not settled so querying',questionsq);
dynamodb.query(questionsq, function(err,quest){
vulog.info('The data:',JSON.stringify(quest));
if(err || !quest ){
vulog.error('Query went wrong',err);
}else{
vulog.info('Query went fine',JSON.stringify(quest));
//return quest;
}
});
}
After this i am expecting this code to work.
var originalQuestions = [];
vulog.debug('--------------------');
if (!quest || quest.length == 0) {
vulog.warn('Questions not entered');
errstr += '\nAt least one interview question must be entered';
} else {
//Goes on
At present i am getting this in at terminal and it get stucked there and there is no further execution.
Query went fine {"Items":[{//Datas}],"Count":2,"ScannedCount":2}
What i want is this i want the first portion of the code to execute and come out with the value of quest then continue code execution. How can i do that Right now it is excecuting and get stuck there
vulog.info('Query went fine',JSON.stringify(quest)); No further executions :)
EDIT:
Async/Await Approach:
async function() {
........
var quest = await queryQuestion();
var originalQuestions = [];
vulog.debug('--------------------');
if (!quest || quest.length == 0) {
vulog.warn('Questions not entered');
errstr += '\nAt least one interview question must be entered';
} else {
//Goes on
}
}
Original Answer:
You can wrap the first logic in a function and return a Promise which in turn will resolve to quest or reject with error
function queryQuestion() {
return new Promise(function(resolve, reject) {
if (typeof(req.body.question) == 'object' || typeof(req.body.question) == 'string') {
quest = req.body.question;
resolve(quest);
} else {
questionsq = {
'ConsistentRead': true,
TableName: 'testquestions',
KeyConditionExpression: "testid = :testid",
ExpressionAttributeValues: {
":testid": {
'S': 'f3b21bf0-d6b9-11e8-bdf1-f7fcc44e7f9c'
}
}
};
vulog.info('Question is not settled so querying', questionsq);
dynamodb.query(questionsq, function(err, quest) {
vulog.info('The data:', JSON.stringify(quest));
if (err || !quest) {
vulog.error('Query went wrong', err);
reject(error);
} else {
vulog.info('Query went fine', JSON.stringify(quest));
//return quest;
resolve(quest);
}
});
}
})
}
And then call this function
queryQuestion().then(function(quest){
var originalQuestions = [];
vulog.debug('--------------------');
if (!quest || quest.length == 0) {
vulog.warn('Questions not entered');
errstr += '\nAt least one interview question must be entered';
} else {
//Goes on
}
}, function(error){
// Handle errors
})
Hope this helps you.
I'm trying to convert an existing API to work with RxJS... fairly new to node, and very new to RxJs, so please bear with me.
I have an existing API (getNextMessage), that either blocks (asynchronously), or returns a new item or error via a node-style (err, val) callback, when the something becomes available.
so it looks something like:
getNextMessage(nodeStyleCompletionCallback);
You could think of getNextMessage like an http request, that completes in the future, when the server responds, but you do need to call getNextMessage again, once a message is received, to keep getting new items from the server.
So, in order to make it into an observable collection, I have to get RxJs to keep calling my getNextMessage function until the subscriber is disposed();
Basically, I'm trying to create my own RxJs observable collection.
The problems are:
I don't know how to make subscriber.dispose() kill the async.forever
I probably shouldn't be using async.forever in the first place
I'm not sure I should be even getting 'completed' for each message - shouldn't that be at the end of a sequence
I'd like to eventually remove the need for using fromNodeCallback, to have a first class RxJS observable
Clearly I'm a little confused.
Would love a bit of help, thanks!
Here is my existing code:
var Rx = require('rx');
var port = require('../lib/port');
var async = require('async');
function observableReceive(portName)
{
var observerCallback;
var listenPort = new port(portName);
var disposed = false;
var asyncReceive = function(asyncCallback)
{
listenPort.getNextMessage(
function(error, json)
{
observerCallback(error, json);
if (!disposed)
setImmediate(asyncCallback);
}
);
}
return function(outerCallback)
{
observerCallback = outerCallback;
async.forever(asyncReceive);
}
}
var receive = Rx.Observable.fromNodeCallback(observableReceive('rxtest'));
var source = receive();
var subscription = source.forEach(
function (json)
{
console.log('receive completed: ' + JSON.stringify(json));
},
function (error) {
console.log("receive failed: " + error.toString());
},
function () {
console.log('Completed');
subscription.dispose();
}
);
So here's probably what I would do.
var Rx = require('Rx');
// This is just for kicks. You have your own getNextMessage to use. ;)
var getNextMessage = (function(){
var i = 1;
return function (callback) {
setTimeout(function () {
if (i > 10) {
callback("lawdy lawd it's ova' ten, ya'll.");
} else {
callback(undefined, i++);
}
}, 5);
};
}());
// This just makes an observable version of getNextMessage.
var nextMessageAsObservable = Rx.Observable.create(function (o) {
getNextMessage(function (err, val) {
if (err) {
o.onError(err);
} else {
o.onNext(val);
o.onCompleted();
}
});
});
// This repeats the call to getNextMessage as many times (11) as you want.
// "take" will cancel the subscription after receiving 11 items.
nextMessageAsObservable
.repeat()
.take(11)
.subscribe(
function (x) { console.log('next', x); },
function (err) { console.log('error', err); },
function () { console.log('done'); }
);
I realize this is over a year old, but I think a better solution for this would be to make use of recursive scheduling instead:
Rx.Observable.forever = function(next, scheduler) {
scheduler = scheduler || Rx.Scheduler.default,
//Internally wrap the the callback into an observable
next = Rx.Observable.fromNodeCallback(next);
return Rx.Observable.create(function(observer) {
var disposable = new Rx.SingleAssignmentDisposable(),
hasState = false;
disposable.setDisposable(scheduler.scheduleRecursiveWithState(null,
function(state, self) {
hasState && observer.onNext(state);
hasState = false;
next().subscribe(function(x){
hasState = true;
self(x);
}, observer.onError.bind(observer));
}));
return disposable;
});
};
The idea here is that you can schedule new items once the previous one has completed. You call next() which invokes the passed in method and when it returns a value, you schedule the next item for invocation.
You can then use it like so:
Rx.Observable.forever(getNextMessage)
.take(11)
.subscribe(function(message) {
console.log(message);
});
See a working example here
I give up on this. May some of the wise stackoverflow monks please fix my bugs?
Code is self explaining. Client sends room names, server does a redis lookup and pushes valid rooms to the array. After adding all the rooms, the list should be emitted to the client.
Problem is closure, async etc. based. I understand the problem but cannot get a workaround because the array needs to remain inside the function. Tricky.
Code:
function roomList(socket){
var roomlist = [], rooms = getRooms(), p = /pChannel_/;
redis.select(7, function(err,res){
for (var k in rooms){
if(rooms[k] != '' && p.test(rooms[k])){
var key = 'channel:'+rooms[k];
redis.hgetall(key, function (err, reply) {
if(reply){
var c = io.sockets.manager.rooms[rooms[k]];
roomlist.push( Array(reply['name'],c.length,reply['icon']) );
}
else { console.log('nothing found'); }
});
}
}
// here be dragons
console.log(roomlist);
socket.emit('roomList', roomlist);
});
}
Thanks.
C'mon guys. The OP explicitly said she/he is interested by understanding how things are supposed to work. And you don't need Q or async or any other 3rd party modules to implement this.
In the initial code, there are two problems:
with Javascript, the closure scope is at function level, not block level. A function must be introduced to define a proper closure. Here, a simple forEach can be used.
the final step (i.e. emit) is not run after the replies have been received from Redis. It must be called in the loop itself. In order to achieve it, it is required to count the items so that the inner callback can test whether the process is complete or not.
So here is another version:
function roomList(socket){
var roomlist = [], rooms = getRooms(), p = /pChannel_/;
redis.select(7, function(err,res){
var count = rooms.length
rooms.forEach( function(r) {
if( r != '' && p.test(r) ) {
var key = 'channel:'+r
redis.hgetall(key, function (err, reply) {
if(reply) {
var c = io.sockets.manager.rooms[r];
roomlist.push( Array(reply['name'],c.length,reply['icon']) );
} else {
console.log('nothing found');
}
if ( --count <= 0 ) {
// here be dragons
console.log(roomlist);
socket.emit('roomList', roomlist);
}
});
} else --count;
});
});
}
Looks like a job for async.map:
function roomList(socket){
var rooms = getRooms(), p = /pChannel_/;
redis.select(7, function(err, res) {
async.map(rooms, function(room, callback) {
if (room === '' || ! p.test(room))
return callback(null, null);
var key = 'channel:' + room;
var c = io.sockets.manager.rooms[room];
redis.hgetall(key, function (err, reply) {
if (err)
callback(err); // propagate Redis errors to final callback, don't know
// if you want that or not; use 'callback(null)' if not.
else
if (reply)
callback(err, Array(reply.name, c.length, reply.icon) );
else
callback(err, null);
});
}, function(err, roomlist) {
if (err)
// handle Redis errors...
// filter 'null' entries from roomlist
roomlist = roomlist.filter(function(room) { return room !== null });
console.log(roomlist);
socket.emit('roomList', roomlist);
});
});
}
(untested)
If you just want to wait for the room list to be fully built before emitting the response (as seems highly reasonable), and assuming Q to be available, then you should just need a few additional lines of Q magic plus a closure-forming wrapper around the inner code to maintain a reliable reference to a Q deferred at each pass of the for loop.
function roomList(socket) {
redis.select(7, function(err, res) {
var list = [],
rooms = getRooms(),
p = /pChannel_/,
promises = [];
for(var k in rooms) {
if(rooms[k] != '' && p.test(rooms[k])) {
(function(dfrd) {
promises.push(dfrd.promise);
var key = 'channel:' + rooms[k];
redis.hgetall(key, function(err, reply) {
if(reply) {
var c = io.sockets.manager.rooms[rooms[k]];
list.push( [reply['name'], c.length, reply['icon']] );
}
else {
console.log('nothing found');
}
dfrd.resolve();
});
})(Q.defer());
}
}
Q.all(promises).then(function() {
console.log(list);
socket.emit('roomList', list);
});
});
}
Check below algorithm...
users = getAllUsers();
for(i=0;i<users.length;i++)
{
contacts = getContactsOfUser(users[i].userId);
contactslength = contacts.length;
for(j=o;j<contactsLength;j++)
{
phones = getPhonesOfContacts(contacts[j].contactId);
contacts[j].phones = phones;
}
users[i].contacts = contacts;
}
return users;
I want to develop such same logic using node.js.
I have tried using async with foreach and concat and foreachseries functions. But all fail in the second level.
While pointer is getting contacts of one user, a value of i increases and the process is getting started for next users.
It is not waiting for the process of getting contacts & phones to complete for one user. and only after that starting the next user. I want to achieve this.
Actually, I want to get the users to object with proper
Means all the sequences are getting ruined, can anyone give me general idea how can I achieve such a series process. I am open to change my algorithm also.
In node.js you need to use asynchronous way. Your code should look something like:
var processUsesrs = function(callback) {
getAllUsers(function(err, users) {
async.forEach(users, function(user, callback) {
getContactsOfUser(users.userId, function(err, contacts) {
async.forEach(contacts, function(contact, callback) {
getPhonesOfContacts(contacts.contactId, function(err, phones) {
contact.phones = phones;
callback();
});
}, function(err) {
// All contacts are processed
user.contacts = contacts;
callback();
});
});
}, function(err) {
// All users are processed
// Here the finished result
callback(undefined, users);
});
});
};
processUsers(function(err, users) {
// users here
});
You could try this method without using async:
function getAllUserContacts(users, callback){
var index = 0;
var results = [];
var getUserContacts = function(){
getContactsOfUser(users[index].userId, function(contacts){
var index2 = 0;
var getContactsPhones = function(){
getPhonesOfContacts(contacts[index2].contactId, function(phones){
contacts[index2].phones = phones;
if(index2 === (contacts.length - 1)){
users[index].contacts = contacts;
if(index === (users.length - 1)){
callback(users)
} else {
index++;
getUserContacts();
}
}else{
index2++;
getContactsPhones();
}
});
}
getContactsPhones();
});
}
getUserContacts();
}
//calling the function
getAllUsers(function(users){
getAllUsersWithTheirContacts(users, function(usersWithContacts){
console.log(usersWithContacts);
})
})
//Asynchronous nested loop
async.eachSeries(allContact,function(item, cb){
async.eachSeries(item,function(secondItem,secondCb){
console.log(secondItem);
return secondCb();
}
return cb();
},function(){
console.log('after all process message');
});