Obtain data from a GET in superagent function - node.js

I'm trying to do something like this:
var countElemnts = function() {
superagent
.get('/someOtherUrl')
.query({
type: value.type
})
.end(function(err, res) {
console.log(JSON.stringify(res.body));
if (res.ok) {
reply(JSON.stringify(res.body));
} else {
console.log(err);
}
})
};
superagent
.post('/someUrl')
.type('json')
.send({
name: 'value.update',
data: {
id: request.params.id,
type: value.type,
count: countElemnts()
}
})
.end(function() {
reply({
message: 'ok'
});
});
In data option of the send function I'm trying to call a function to obtain some value.
What I want is to get the value that comes in the body of a reply, ie the res.body. Upon console.log get this [{ "count ": 3 } ], but if I do a console.log of res.body.count tells me undefined, what could I do to get the value 3.
Thanks.

Since the return doesn't have the extra space in "count " (as mentioned in the comments) the problem was that you were trying to access the count attribute of the array and not the object (first element of the array), so to access it you should do it like:
res.body[0].count
As for the problem of not being able to get the count in your POST, the problem is that countElemnts uses an asynchronous function. The end method of superagent takes a function as parameter and it is only called when it receives a response. By that time, your function had already returned (with undefined, since you didn't return anything).
You should first make the GET call and then send it to a function that will handle the POST. For example:
superagent
.get('/someOtherUrl')
.query({
type: value.type
})
.end(function(err, res) {
if (err) {
console.log(err);
} else {
console.log(JSON.stringify(res.body));
sendCount(res.body[0].count)
}
});
function sendCount(count) {
superagent
.post('/someUrl')
.type('json')
.send({
name: 'value.update',
data: {
//id: request.params.id, // not sure where you are getting these values from,
//type: value.type, // but you should adapt it to your code
count: count
}
})
.end(function() {
reply({
message: 'ok'
});
});
}

Related

Execute push notification after sending message

I am using sails.socket for message sending. My requirement is that, I have to send a push notification after the success of sent message. How it is possible. Please see the code that I have written.
sendChatMessage(chatMessage, function() {
// calling push notification when a chat message send
var serverKey = req.options.settingsKeyValue.PUSH_SERVER_KEY;
var typeData = { type:1, data:{type:1} };
var pushData = { title:'New Message', body: data };
pusherService.pushFcm(serverKey,typeData,pushData,toId, function(err, result) {
if(err) {
return res.json(200, {status:1, status_type: 'Success', message: 'Error in sending the message'});
}else{
return res.json(200, {status:1, status_type: 'Success', message: 'You have successfully send the message'});
}
});
});
function sendChatMessage(){
var socketRoom = "userRoom_"+toId;
var roomsSubcribers = sails.sockets.subscribers(socketRoom);
console.log("roomsSubcribers");
console.log(roomsSubcribers);
var data = {
text: message,
from_id: fromId,
from_name: userResult[0].firstName+' '+userResult[0].lastName, from_img : userResult[0].profilePhoto,
};
sails.sockets.broadcast(socketRoom,{
type : "chat",
message : data,
});
callback(chatMessage);
}
These blocks of code look ok to me... you just have to position them in the right place within your application. Have you tried this?
You definitely need to name some of your arguments in both the sendChatMessage function definition, and in your callback function.
Maybe you need something like this:
// in some controller
// notice the named arguments 'options' and 'callback'
var sendChatMessage = function(options, callback){
var socketRoom = "userRoom_"+options.toId;
var roomsSubcribers = sails.sockets.subscribers(socketRoom);
console.log("roomsSubcribers");
console.log(roomsSubcribers);
var data = {
text: options.message,
from_id: options.fromId,
from_name: options.fromName,
from_img : options.fromImg,
};
sails.sockets.broadcast(socketRoom,{
type : "chat",
message : data,
});
callback(data);
};
module.exports = {
someMethod: function(req, res) {
// do some work, including defining / getting all options
var options = {
toId: 123,
fromId: 456,
message: 'test message',
fromName: 'test user',
fromImg: 'some/image.jpg' // don't know if you need a source here or what
};
// invoke your function - notice the named argument in the callback
sendChatMessage(options, function(data) {
// calling push notification when a chat message send
var serverKey = req.options.settingsKeyValue.PUSH_SERVER_KEY;
var typeData = { type:1, data:{type:1} };
var pushData = { title:'New Message', body: data };
pusherService.pushFcm(serverKey,typeData,pushData,toId, function(err, result) {
if(err) {
return res.json(200, {status:1, status_type: 'Success', message: 'Error in sending the message'});
}else{
return res.json(200, {status:1, status_type: 'Success', message: 'You have successfully send the message'});
}
});
});
},
};
I can't know if this is the right way to use all your plugins, etc, but putting the pieces together seems to make sense.
Last note, when you define your own callbacks, it's good practice to make the first argument an error object, and check for received errors in the callback body. When successful you can return a null error, like callback(null, data);.
Hope this is helpful.

What does meen this error: Falsy value for recipient key 'registrationTokens'

I got this error: Falsy value for recipient key 'registrationTokens' while working with gcm push notifications.
Below you can find my code:
Device.find({ user: { $in: users }}, function (err, devices) {
if (err) {
logger.error('500 ' + err)
return res.status(500).json({
code: config.errorCode.status500.code,
message: config.errorCode.status500.message
})
}
var androidRegTokens = []
var iOSReqTokens = []
for (var i = 0; i < devices.length; i++) {
if (devices[i].platform == 'Android') {
androidRegTokens.push(devices[i].deviceToken)
} else {
iOSReqTokens.push(devices[i].deviceToken)
}
}
if (androidRegTokens.length > 0) {
gcmPush('Notification is sent.', androidRegTokens)
}
if (iOSReqTokens.length > 0) {
apnsPush('Notification is sent.', iOSReqTokens)
}
return res.json({ msg: 'Operation succeed.'})
})
Body of the function gcmPush('Notification is sent.', androidRegTokens) is:
this.sender.send(message, { registrationTokens: deviceTokens }, function (err, response) {
if (err) {
console.error(err)
}else {
console.log(response)
}
})
Does anyone know what wrong is with my code? At first push notifications worked perfect, than this error occured each time I call the service.
EDIT:
I have solved this thanx to Luiz Fernando. The problem is the function:
gcmPush('Notification is sent.', androidRegTokens)
I have forgot to add title which is part of the constructor:
function GcmPush (title, message, deviceTokens) {
this.sender = new gcm.Sender(config.gcmSender)
this.sendPushNotification(title, message, deviceTokens)
}
The deviceTokens object is a falsy value, it can be: null, undefined, 0, '', NaN,false.
Maybe it happend because you are filling iOSReqTokens and androidRegTokens nested in an asynchronous operation (it seems Device.find is async). So, the callback of Device.find and the for-loop happens in different times. Probably, the for-loop is happening AFTER the response, so this response will be undefined (and really is).
So, you need to use some async control flow library, such Promise or Async and ensure the correct order of operations.
Also, where you use the pushed registrations?

How can I test my asynchronous code in a restify HTTP handler?

I have a function in my Restify project that handles an HTTP GET request. After some processing it uses Sequelize to find the user entity for my current session. The User.findOne function returns a promise and depending on the result of that promise, I'm sending an HTTP response with 200 or 404.
static getMe(req, res, next) {
const userInfo = BaseController.getUserSession(req);
// hard to test this part
User.findOne({
where: {email: userInfo.email}
}).then(function(user) {
if (user) BaseController.respondWith200(res, user);
else BaseController.respondWith404(res, 'User not found.');
}, function(error) {
BaseController.respondWith404(res, error);
}).then(function() {
return next();
});
}
I've tried a few different libraries to help with testing so I'm sorry if this is a messy combination of things. This is in my beforeEach function for my tests:
const usersFixture = [
{id:2, email:'ozzy#osbourne.com', facebookId:54321, displayName: 'Ozzy Osbourne'},
{id:3, email:'zakk#wylde.com', facebookId:34521, displayName: 'Zakk Wylde'},
{id:4, email:'john#lennon.com', facebookId:12453, displayName: 'John Lennon'}
];
this.findOneSpy = sinon.spy(function(queryObj) {
return new Promise(function(resolve, reject) {
const user = usersFixture.find(function(el) { return el.email === queryObj.where.email });
if (user) resolve(user);
else resolve(null);
});
});
this.respondWith200Spy = sinon.spy(function(res, data) {});
this.respondWith400Spy = sinon.spy(function(res, error) {});
this.respondWith404Spy = sinon.spy(function(res, error) {});
this.controller = proxyquire('../../controllers/user-controller', {
'../models/user': {
findOne: this.findOneSpy
},
'./base-controller': {
respondWith200: this.respondWith200Spy,
respondWith400: this.respondWith400Spy,
respondWith404: this.respondWith404Spy
}
});
And here is what one of my tests looks like:
it('should return 200 with user data if user email matches existing user', function() {
// THIS FUNCTION IS NEVER HIT
this.respondWith200Spy = function(res, data) {
data.should.equal({id:4, email:'john#lennon.com', facebookId:12453, displayName: 'John Lennon'});
done();
};
const req = {session:{user:{email:'john#lennon.com'}}};
this.controller.getMe(req, this.res, this.nextSpy);
this.findOneSpy.should.have.been.called;
});
Since we aren't actually passing a callback to the function and the function doesn't really return anything (just does asynchronous things elsewhere), I can't figure out how to test it to make sure it's working right. Any help is appreciated.
The actual code works just fine. I'm just trying to get some quality unit testing into the project. Thanks!
I ended up finding a way to do it using proxyquire. I just re-stubbed the controller class that I am testing and made the respondWith200 callback make an assertion. Then I created a new spy for the next function that just calls done (which is passed into the test case). I verified that the code is all getting hit.
it('should return 200 with user data if user email matches existing user', function(done) {
const controller = proxyquire('../../controllers/user-controller', {
'../models/user': {
findOne: this.findOneSpy
},
'./base-controller': {
respondWith200: function(res, data) {
data.displayName.should.equal('John Lennon');
},
respondWith400: this.respondWith400Spy,
respondWith404: this.respondWith404Spy
}
});
const req = {grft_session:{user:{email:'john#lennon.com'}}};
const nextSpy = sinon.spy(function() {
done();
});
controller.getMe(req, this.res, nextSpy);
this.findOneSpy.should.have.been.called;
});

Create/update with Sequelize on an array of items

I created a function to:
take an array of 'labels' and look for whether they have a record in the db already
create those which don't exist,
and update those which do exist
return a json array reporting on each item, whether they were updated/created, or resulted in an error
I managed to make it work but I feel like I just made some ugly dogs' dinner!
var models = require("../models");
var Promise = models.Sequelize.Promise;
module.exports = {
addBeans: function (req, callback) {
Promise.map(req.body.beansArr, function (bean) {
return models.Portfolio.findOrCreate({where: {label: bean}}, {label: bean});
}).then(function (results) { // Array of 'instance' and 'created' for each bean "findOrCreate(where, [defaults], [options]) -> Promise<Instance>"
var promisesArr = [];
results.forEach(function (result) {
if (result[1]) { // result[1] = wasCreated
promisesArr.push(Promise.resolve([result[0].dataValues.label, "created"]));
} else {
promisesArr.push(
models.Portfolio.update({label: result[0].dataValues.label},
{where: {label: result[0].dataValues.label}}).then(function () {
return Promise.resolve([result[0].dataValues.label, "updated"])
})
);
}
});
return promisesArr;
// When it's all done create a JSON response
}).then(function (results) {
var resultObj = {items: []}; // JSON to return at the end
Promise.settle(results).then(function (promiseinstances) {
for (var i = 0; i < promiseInstances.length; i++) {
if (promiseInstances[i].isFulfilled()) {
resultObj.items.push({
item: {
label: promiseInstances[i].value()[0],
result: promiseInstances[i].value()[1],
error: ''
}
});
}
else if (promiseInstances[i].isRejected()){
resultObj.items.push({
label: promiseInstances[i].value()[0],
result: 'error',
error: promiseInstances[i].reason()
});
}
}
// Send the response back to caller
}).then(function () {
return callback(null, resultObj);
}, function (e) {
return callback(e, resultObj);
});
});
}
};
Question:
Is there an easier or more obvious way to create/update values with Sequelize?
Is my use of Promise.settle() appropriate for this case? I have the feeling I made this more complicated than it needs to be.
I am new to Sequelize and using Promises, I'd appreciate if someone could advise on this.
I feel like this would work better on CodeReview.SE but I can see a few issues.
Is there an easier or more obvious way to create/update values with Sequelize?
Well, for one thing:
.then(function(array){
var newArr = [];
array.forEach(function(elem){
newArr.push(fn(elem);
}
return newArr;
});
Is just
.map(fn)
Additionally, promises assimilate so you can return val; from a .then you don't have to return Promise.resolve(val);.
So:
).then(function (results) { // Array of 'instance' and 'created' for each bean "findOrCreate(where, [defaults], [options]) -> Promise<Instance>"
var promisesArr = [];
results.forEach(function (result) {
if (result[1]) { // result[1] = wasCreated
promisesArr.push(Promise.resolve([result[0].dataValues.label, "created"]));
} else {
promisesArr.push(
models.Portfolio.update({label: result[0].dataValues.label},
{where: {label: result[0].dataValues.label}}).then(function () {
return Promise.resolve([result[0].dataValues.label, "updated"])
})
);
}
});
return promisesArr;
})
Is just
.map(function(result){
if(result[1]) return [result[0].dataValues.label, "created"];
return models.Portfolio.update({label: result[0].dataValues.label},
{where: {label: result[0].dataValues.label}}).
return([result[0].dataValues.label, "updated"]);
});
However, since you want it to work regardless of it being resolved, you'd have to do:
.then(function(results){
return results.map(function(result){
if(result[1]) return [result[0].dataValues.label, "created"];
return models.Portfolio.update({label: result[0].dataValues.label},
{where: {label: result[0].dataValues.label}}).
return([result[0].dataValues.label, "updated"]);
});
});
Which means it'll resolve regardless, then you'd call .settle():
.settle().then(function(results){
// your settle logic here
});
Note that the last:
}).then(function () {
return callback(null, resultObj);
}, function (e) {
return callback(e, resultObj);
});
Is simply:
.nodeify(callback);
However, I recommend sticking to promises.
I use Promise.settle for sequelize.update, and can get affect rows number by _settledValueField .
promise.push(...update...)
db.sequelize.Promise.settle(promise).then(function (allresult) {
var affectcnt = 0
allresult.forEach(function (singlecnt) {
if (undefined !== singlecnt._settledValueField[1]) {
affectcnt += parseInt(singlecnt._settledValueField[1])
}
})
unfortunately, it's only work for update.
You can insert array in database using sequelize. You want to change in model like below. I am trying to add multiple languages in database through array.
language: { type: DataTypes.STRING,
allowNull: false,
get()
{
return this.getDataValue('language').split(';')
},
set(val)
{
this.setDataValue('language',Array.isArray(val) ? val.join(','):val);
}
}

How to make a block of code work synchronously

Here is my code :
server.get(url_prefix + '/user/:user_id/photos', function(req, res, next) {
if (!req.headers['x-session-id']) {
res.send({
status: {
error: 1,
message: "Session ID not present in request header"
}
})
} else {
User.findOne({
session_id: req.headers['x-session-id']
}, function(err, user) {
if (user) {
var user_id = req.params.user_id
Album.find({userId : user_id})
.populate('images')
.exec(function (err, albums) {
if (albums) {
albums.forEach(function(album, j) {
var album_images = album.images
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if(like){
albums[j].images[i].userLike = true;
}
})
})
})
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
})
} else
return notify_error(res, "No Results", 1, 404)
})
}
else {
res.send({
status: {
error: 1,
message: "Invalid Session ID"
}
})
}
})
}
})
I am trying to add a extra value (albums[j].images[i].userLike = true;) to my images array, which is inside album array.
The problem is return res.send({ send the data before we get response from the foreach
How can I make it work, so that return should happen only after foreach has completed all the iteration
You will have to wait with invoking res.send until you fetched all the likes for all the images in each of the albums. E.g.
var pendingImageLikes = album_images.length;
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if (like) {
albums[j].images[i].userLike = true;
}
if (!--pendingImageLikes) {
// we fetched all likes
res.send(
// ...
);
}
});
You might need to special case for album_images.length === 0.
Also, this does not take into account that you have multiple albums with multiple images each. You would have to delay res.send there in a very similar way to make this actually work. You might want to consider using a flow control library like first (or any other of your preference, just search for "flow control library") to make this a bit easier.
Also, you might want to consider not relying on semicolon insertion and manually type your semicolons. It prevents ambiguous expressions and makes the code easier to read.
Since you need your code to wait until all of the find operations have completed, I'd suggest you consider using the async package, and specifically something like each (reference). It makes using async loops cleaner, especially when dealing with MongoDB documents and queries. There are lots of nice features, including the ability to sequentially perform a series of functions or waterfall (when you want to perform a series, but pass the results from step to step).
> npm install async
Add to your module:
var async = require("async");
Your code would look something like this:
albums.forEach(function(album, j) {
async.each(album.images, function(album, done) {
Like.findOne({imageID: image._id, userIDs:user._id}, function(err, like){
if(!err && like){
albums[j].images[i].userLike = true;
}
done(err); // callback that this one has finished
})
})
}, function (err) { // called when all iterations have called done()
if (!err) {
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
});
}
return notify_error(res, "No Results", 1, 404);
});
});

Resources