How to emit first then callback? - javascript

I can not let emiter emits the signal which is returned from pg module before got called back. Here is my situation.
questionHandler_upqid.js :
var express = require('express');
var router = express.Router();
var pg = require('pg');
var connectionString = process.env.DATABASE_URL || 'postgres://admin:admin#localhost:5432/mydb';
var question_id;
var resource_id = [];
//var err_ = []; //Move to local function
var mcres = [];
exports.addResourceFile = function(req, res, err, client, done, callback){
var cb_is_func = (typeof(callback)==='function');
console.log('addResourceFile function called');
var err_ = [];
var resourcefiles = req.body.resourcefiles;
var idx = 0;
//Use local function because of err + client + done scope.
exports.addResourceFile_inside = function (element, index, array){
console.log('addResourceFile_inside function called, index : ' + index);
var func_ = 'SELECT Dugong.resourcefile_Add($1,$2,$3)';
//rfdata = resource file data.
var rfdata = [element.fileurl,
element.name,
element.type];
console.log(rfdata);
var addResourceFile_ = client.query(func_, rfdata);
//Add underscore (_) to prevent confusion.
addResourceFile_.on('error', function(err){
console.log('error signal from addResourceFile');
var tempdata = {success : false,
id : -1,
reason : {errmsg : err.detail,
errid : 'addResourceFile' + index }}; //plus index to spot record
err_.push(tempdata);
});
addResourceFile_.on('row', function(row){
console.log('row signal from addResourceFile_');
resource_id.push(row.resourcefile_add);
});
console.log('hello world');
callback(err_, resource_id);
return ;
};
/*
//Want sequential run of these.
//But it does not.
resourcefiles.forEach(exports.addResourceFile_inside);
callback(err_, resource_id);
return ;
*/
//Emitter emits lastest. callback executed first.
exports.action = function(){
resourcefiles.forEach(exports.addResourceFile_inside);
};
exports.summary = function(){
callback(err_, resource_id);
};
exports.summary(
exports.action()
);
};
My unit test : questionHandler_upqid.js
var httpMocks = require('node-mocks-http');
var test = require('unit.js');
var real_questionHandler = require('../routes/questionHandler_upqid.js');
var proxyquire = require('proxyquire'),
pgStub = { };
var events = require('events');
describe('questionHandler_upqid : ', function () {
var request;
beforeEach(function (){
request = httpMocks.createRequest({
method: 'PUT',
url: '/questions',
params: {id : 1 },
body : { questiontype : 'Multiple Choice',
problemtext : 'Find x when x is 4 + 2',
excerpttext : 'Somewhere I belong',
solutiontext : '6',
authorid: 'Sarit1',
subjectid: '2',
lastmod: '2099-0101 00:00:02',
resourcefiles : [{"name":"penguin","type":"jpeg","fileurl":"/1.jpeg"},
{"name":"penguin","type":"jpeg","fileurl":"/2.jpeg"},
{"name":"penguin","type":"jpeg","fileurl":"/3.jpeg"},
{"name":"penguin","type":"jpeg","fileurl":"/4.jpeg"},
{"name":"penguin","type":"jpeg","fileurl":"/5.jpeg"}],
mcresponses : [{"text":"BX","image":"A1","iscorrect":false},
{"text":"BW","image":"A2","iscorrect":false},
{"text":"ACRA","image":"A3","iscorrect":true},
{"text":"GxM","image":"A4","iscorrect":false}]
//Do not change lenght of mcresponse.
//mcres_length is used in 'should add MCResponse with error'
}
});
pgStub.end = function(){};
});
it('should add ressource file with error', function(done){
//emitter emit 'error'
var client = {};
client.query = function(querystr, data){
var emitter = new events.EventEmitter();
setTimeout(function(){
console.log('emit diode add resource file with error');
error = {detail : 'Resrc file can be changed!'};
emitter.emit('error', error);
},0);
console.log('add emitters');
return emitter;
};
var response = httpMocks.createResponse();
var questionHandler_upqid = proxyquire('../routes/questionHandler_upqid.js', { 'pg' : pgStub} );
questionHandler_upqid.addResourceFile(request, response, null, client, null, function(err, resrc_id){
console.log(err);
console.log(resrc_id);
done();
});
});
});
This code still emit after callback has been called.
var p1 = new Promise(function(){
resourcefiles.forEach(exports.addResourceFile_inside);
});
p1.then(callback(err_, resource_id));
I got 2 blank arrays of err_ and resource_id. Then come up with signal from emitter. Actually I want emitter to be executed first then callback. The callback should return me 2 arrays. They are err_ and resource_id.

You'll want to tie the callback to one of the pg's events to know that the query has completed before invoking it.
Along with 'row' and 'error' events, its Query objects will also emit an 'end' event when all rows have been received successfully:
addResourceFile_.on('row', function(row){
console.log('row signal from addResourceFile_');
resource_id.push(row.resourcefile_add);
});
addResourceFile_.on('end', function () {
callback(null, resource_id);
});
Though, the 'end' event won't be emitted if there are errors (so you can safely assume null), so you'll want to invoke the callback from the 'error' event as well to cover both success and failure:
addResourceFile_.on('error', function (err) {
console.log('error signal from addResourceFile');
var tempdata = {
success : false,
id : -1,
reason : {
errmsg : err.detail,
errid : 'addResourceFile' + index // plus index to spot record
}
};
callback(tempdata, null);
});

I don't know this is a good coding style or not, but just for now. I go by this.
addResourceFile_.on('error', function(err){
console.log('error signal from addResourceFile');
var tempdata = {success : false,
id : -1,
reason : {errmsg : err.detail,
errid : 'addResourceFile' + index }}; //plus index to spot record
err_.push(tempdata);
console.log(idx);
if(idx === array.length - 1){
callback(err_, resource_id);
}
idx = idx + 1;
});
addResourceFile_.on('row', function(row){
console.log('row signal from addResourceFile_');
resource_id.push(row.resourcefile_add);
if(idx === array.length - 1){
callback(err_, resource_id);
}
idx = idx + 1;
});

Related

How to trigger firebase http function in node.js?

I am trying to trigger an another function in Firebase Cloud function with javascript. But i always getting an error of Can't set headers after they are sent. Please take a look at my code below: ................. ................. ............ ................ ................. ............... ....................... .................. ..............
exports.productIndexShuffleOne = functions.https.onRequest(async (req, res) => {
const interval = req.query.interval;
console.log("interval: "+interval);
const productRef = admin.firestore().collection("Products");
const adminRef = admin.firestore().collection("Admin").doc("totalProd").get();
const dateRef = admin.firestore().collection("Admin").doc("totalProd").collection("indexShuffle").doc("productShuffle").get();
return dateRef.then(documentSnapshot => {
const setDate = documentSnapshot.get('date').seconds;
var nextDay = setDate;
console.log("Date: "+nextDay);
const x = setInterval(function() {
clearInterval(x);
return Promise.all([adminRef]).then(result => {
const totalNum = result[0].data().totalNumber;
console.log("totalNum: "+totalNum);
var numberList = [];
var index = 1;
while(index <= totalNum){
numberList.push(index);
index++;
}
var cidx, ridx, tmp;
cidx = numberList.length;
while (cidx !== 0) {
ridx = Math.floor(Math.random() * cidx);
cidx--;
tmp = numberList[cidx];
numberList[cidx] = numberList[ridx];
numberList[ridx] = tmp;
}
console.log(numberList);
var counter = 0;
return productRef.get().then(snapshot => {
snapshot.forEach(doc => {
const prodID = doc.get('productID');
const index = doc.get('index');
var newIndex = numberList[counter];
counter++;
console.log("oldIndex: "+index);
console.log("newIndex: "+newIndex);
productRef.doc(prodID).update({
index: newIndex
}, {merge: true});
});
return res.redirect('https://us-central1-myfunction-123456.cloudfunctions.net/productIndexShuffleTwo?interval='+interval);
})
.catch(err => {
console.log('Error getting documents', err);
});
});
}, interval);
return res.status(203).send(interval);
}).catch(function(err) {
console.error(err);
});
});
This is because you've sent multiple responses while the rule is that you only allowed sending one response. Please try to look at your code and optimize it in such a way that it contains only one response.
I can see you have multiple responses as below:
1 -> return res.redirect('https://us-central1-myfunction-123456.cloudfunctions.net/productIndexShuffleTwo?interval='+interval);
2 -> return res.status(203).send(interval);
I believe that you can have res.redirect and then res.status.send called one after another. When you writing endpoints there rule of a thumb: always send response and only do that once. Refactor your code so there no way you can make those two calls, but only one of them.

aws sqs: queuing in a loop

In code below, I have a main loop to get each 'assunto' in records(jsonAssuntos). After that, I want to send the 'assunto' to your appropriate queue. The problem is about idSequence. The queue receive the wrong value, sometimes the last number generated.
module.exports.consumeMainQueue = async (event, context, callback) => {
var AssuntoQueueAURL = 'http://localhost:9324/queue/Queue';
for (let recordIndex in event.Records){
var record = event.Records[recordIndex];
var jsonAssuntos = JSON.parse(record.body);
var idMessage = record.messageId;
var idSequence = 0; // initialize *********************
for (var assuntoIndex in jsonAssuntos.dat){
idSequence++; // increment ********************
var assunto = jsonAssuntos.dat[assuntoIndex];
if(assunto.typ == "typ1" || assunto.typ == "typ2" || assunto.typ == "typ3"){
var infoAssunto = {
ParentID: idMessage,
Asssunto: assunto
IdSequence : idSequence; // send to queue **************
};
var paramsAssuntoQueue = {
MessageBody: JSON.stringify(infoAssunto),
QueueUrl: assuntoTyp
};
queue.sendMessage(paramsAssuntoQueue, function(err, data) {
if (err) {
console.log("Error", err);
}
else{
console.log("OK");
//End Log
};
});
}
else{
}
}
};

How to callback an error message when searching a table is unsuccessful in DynamoDB

I'm currently using AWS Lambda JavaScript code to try and search a DynamoDB table this is then implemented into an Amazon Alexa application, but that isn't really important for what I'm asking. Here is the code I'm struggling with:
function readDynamoItem(params2, callback) {
var AWS = require('aws-sdk');
AWS.config.update({region: AWSregion});
var dynamodb = new AWS.DynamoDB();
console.log('reading item from DynamoDB table');
dynamodb.scan(params2, function (err, data){
if (err) {
callback("error");
//console.log(err, err.stack); // an error occurred
}
else{
callback(data);
}
});
}
So when an error occurs I want it to callback the message "error" and then use it here:
const params2 = {
TableName: 'Fixtures',
FilterExpression: 'team1 = :value',
ExpressionAttributeValues: {':value': {"S": MyQuestion.toLowerCase()}}
};
readDynamoItem(params2, myResult=>{
say = myResult;
this.response.speak(say).listen('try again');
this.emit(':responseReady');
});
All I'm getting at the moment is this response when I test, I think due to err just ending the program instead of calling the error back to use in the implementation:
Response:
{
"errorMessage": "RequestId: 0f586880-2ddb-11e8-bdf7-07b4c224b25d Process exited before completing request"
}
Any help would be greatly appreciated.
Here's the full code for my project for further reference:
const AWSregion = 'eu-west-1';
const Alexa = require('alexa-sdk');
const AWS = require('aws-sdk');
AWS.config.update({
region: AWSregion
});
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
// alexa.appId = 'amzn1.echo-sdk-ams.app.1234';
// alexa.dynamoDBTableName = 'YourTableName'; // creates new table for session.attributes
alexa.registerHandlers(handlers);
alexa.execute();
};
const handlers = {
'LaunchRequest': function () {
this.response.speak('welcome to magic answers. ask me a yes or no question.').listen('try again');
this.emit(':responseReady');
},
'MyIntent': function () {
var MyQuestion = this.event.request.intent.slots.MyQuestion.value;
console.log('MyQuestion : ' + MyQuestion);
const params2 = {
TableName: 'Fixtures',
FilterExpression: 'team1 = :value',
ExpressionAttributeValues: {':value': {"S": MyQuestion.toLowerCase()}}
};
const params3 = {
TableName: 'Fixtures',
FilterExpression: 'team2 = :value',
ExpressionAttributeValues: {':value': {"S": MyQuestion.toLowerCase()}}
};
readDynamoItem(params2, myResult=>{
var say = MyQuestion;
//if nothing is found when scanning for team1, scan team2
if (myResult == "error"){
readDynamoItem(params3, myResult2=>{
say = myResult2;
say = 'The top scorer for ' + MyQuestion + ' is ' + myResult2;
this.response.speak(say).listen('try again');
this.emit(':responseReady');
});
}
else{
say = myResult;
say = 'The top scorer for ' + MyQuestion + ' is ' + myResult;
this.response.speak(say).listen('try again');
this.emit(':responseReady');
}
});
},
'AMAZON.HelpIntent': function () {
this.response.speak('ask me a yes or no question.').listen('try again');
this.emit(':responseReady');
},
'AMAZON.CancelIntent': function () {
this.response.speak('Goodbye!');
this.emit(':responseReady');
},
'AMAZON.StopIntent': function () {
this.response.speak('Goodbye!');
this.emit(':responseReady');
}
};
// END of Intent Handlers {} ========================================================================================
// Helper Function =================================================================================================
//reading the Fixtures table
function readDynamoItem(params2, callback) {
var AWS = require('aws-sdk');
AWS.config.update({region: AWSregion});
var dynamodb = new AWS.DynamoDB();
var team1;
var team2;
console.log('reading item from DynamoDB table');
dynamodb.scan(params2, function (err, data){
if (err) {
callback("error");
//callback("error");
//console.log(err, err.stack); // an error occurred
}
else{
console.log(data); // successful response
team1 = jsonToString(data.Items[0].team1);
team2 = jsonToString(data.Items[0].team2);
var t1goals = jsonToString(data.Items[0].t1goals);
var t2goals = jsonToString(data.Items[0].t2goals);
t1goals = parseInt(t1goals);
t2goals = parseInt(t2goals);
var search;
var chosenValue = Math.random() < 0.5 ? team1 : team2;
// if goals are equal in a match then it is random which team will score next
if(t1goals == t2goals){
search = chosenValue;
}
//if a team has 1 goal more than the other then it is a 3rd more likely they will score next
else if(t1goals > t2goals && t1goals == 1){
if(randomInt(1, 3) == 1){
search = team2;
}
else{
search = team1;
}
}
else if(t2goals > t1goals && t2goals == 1){
if(randomInt(1, 3) == 1){
search = team1;
}
else{
search = team2;
}
}
//if a team has more than 1 goal more than the other then it is a 5th more likely they will score next
else if(t1goals > t2goals && t1goals > 1){
if(randomInt(1, 5) == 1){
search = team2;
}
else{
search = team1;
}
}
else if(t2goals > t1goals && t2goals > 1){
if(randomInt(1, 5) == 1){
search = team1;
}
else{
search = team2;
}
}
var params = {
TableName: 'yesno',
FilterExpression: 'team = :value',
ExpressionAttributeValues: {':value': {"S": search}}
};
readDynamoFixtures(params, myResult=>{
callback(myResult);
});
}
});
}
//read player details from the the yesno table
function readDynamoFixtures(params, callback) {
var goals = new Array();
var playing = new Array();
var messages = new Array();
var most = 0;
var mostMessage;
var dynamodb = new AWS.DynamoDB();
dynamodb.scan(params, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else{
for(var i = 0; i <= (data.Count - 1); i++){
console.log(data); // successful response
var temp = jsonToString(data.Items[i].playername);
messages[i] = temp;
temp = jsonToString(data.Items[i].goals);
temp = parseInt(temp);
goals[i] = temp;
temp = jsonToString(data.Items[i].playing);
playing[i] = temp;
//compare each players goals
if (goals[i] > most && playing[i] == "true"){
most = goals[i];
mostMessage = messages[i];
}
}
}
callback(mostMessage);
});
}
//convert database items from json format to string
function jsonToString(str){
str = JSON.stringify(str);
str = str.replace('{\"S\":\"', '');
str = str.replace('\"}', '');
return str;
}
//get a random int between min and max
function randomInt(min,max)
{
return Math.floor(Math.random()*(max-min+1)+min);
}
Edit:
I have tried testing this code with .query instead of .scan and the error callback works perfectly which is strange but obviously for this implementation I need to use .scan
When you get the "Process exited" response from the Lambda it is helpful to log heavily to see where the Lambda is getting stuck and then check the Cloudwatch Logs to get to the detail.
Then you can pinpoint the exception and focus on it. At least for me, the root cause was many times unexpected as Lambdas force a different way of thinking.

Braintree Transaction.search in Meteor Server

How can I wait for the Braintree Transaction.search() function to return all data.
Right now it does not wait and just comes back with undefined return value.
Here is the code
I tried to use Meteor.asynwrap but that also does not work.
`
function getTrxns(cid) {
var future = new Future();
var trxns = [];
var i = 0
var stream = gateway.transaction.search(function (search) {
r = search.customerId().is(cid)});
stream.on("data", function(data){
i = i+1
trxns.push({
'id':data.id,
'amount':data.amount,
'crtDt': data.createdAt,
'ccType': data.creditCard.cardType,
'currency': data.currencyIsoCode,
'last4': data.creditCard.last4,
'expdt': data.creditCard.expirationDate
});
});
stream.on("end", function(){
// print the output in console
console.log('End Stream cnt: '+i);
return trxns;
});
stream.resume();
}
Meteor.methods({
findCustTrxns: function() {
var btId = Meteor.user().custBtId;
if (!btId) { return []; };
console.log('findCustTrxns cusBtId: '+btId);
var xx = getTrxns(btId);
console.log('xx len :'+xx.length);
}
});
OUTPUT is:
I20170509-15:22:09.095(0)? findCustTrxns cusBtId: 232057823
I20170509-15:22:09.095(0)? Exception while invoking method 'findCustTrxns' TypeError: Cannot read property 'length' of undefined
I20170509-15:22:09.095(0)? End Stream cnt: 56
Found a way to make it work:
1. Added a callback function
function getTrxns(cid,callback )
2. invoked the callback in stream.on('end;..) Here is the code
function getTrxns(cid,callback ) {
var trxns = [];
var i = 0
var stream = gateway.transaction.search(function (search) {
r = search.customerId().is(cid)});
stream.on("data", function(data){
i = i+1
trxns.push({
'id':data.id,
});
});
stream.on("end", function(){
// print the output in console
console.log('End Stream cnt: '+i);
callback('', trxns);
});
stream.resume();
}
3. Changed the Meteor Method :
findCustTrxns: function(btId) {
if (!btId) { return []; };
console.log('findCustTrxns cusBtId: '+btId);
var trxns = [];
var i = 0;
var fn = Meteor.wrapAsync(getTrxns); //made it a synchronous call
try {
var res = fn(btId);
if (res) {
console.log('Got data from getTrxns ');
return res;
}
} catch( err) {
console.log('Error calling hetTrxns '+err);
}
}, //findCustTrxns
Now I am able to get the Transactions. Hope it helps

Sinon.stub() return different values every time it's called

Here is the code I am writing tests for:
'use strict';
var internals = {};
var _ = require('lodash');
module.exports = {
initialize: function (query) {
internals.query = query;
},
createField: function (fieldId, accountId, payload) {
function callQuery (parList) {
var query = 'INSERT into fields VALUES (:uuid, :accountId, :shortcutName, :displayName, :fieldType, :widgetType, :columnOrder, :options, :required, NULL)';
return internals.query(query, parList, function () { return fieldId; });
}
var increment = 10;
var parameterList = {
'uuid': fieldId,
'accountId': accountId,
'shortcutName': payload.shortcutName,
'displayName': payload.displayName,
'fieldType': payload.fieldType,
'widgetType': payload.widgetType,
'columnOrder': payload.columnOrder,
'options': JSON.stringify(payload.options) || null,
'required': payload.required || 'f'
};
if (!payload.columnOrder) {
var columnQuery = 'SELECT MAX(column_order) from fields';
return internals.query(columnQuery, {}, function (x) {return x; })
.then(function (results) {
var highestColumnOrder = results[0]['MAX(column_order)'];
var newHighestColumnOrder = Math.ceil(highestColumnOrder / 10) * 10;
if (newHighestColumnOrder > highestColumnOrder) {
parameterList.columnOrder = newHighestColumnOrder;
} else {
parameterList.columnOrder = newHighestColumnOrder + increment;
}
return callQuery(parameterList);
});
} else {
return callQuery(parameterList);
}
},
getFieldsByAccountId: function(accountId, showDeleted) {
var callQuery = function(paramList) {
var query = 'SELECT ' + paramList.columns.join(", ") + ' FROM fields WHERE account_id = :account_id';
if (!showDeleted) {
query += ' AND archived_at IS NULL';
}
return internals.query(query, paramList, function(rows) {
return _.each(rows, function(row) {
if(row.options) {
row.options = JSON.parse(row.options);
}
row.required = !!row.required;
});
});
};
var columnList = ["uuid", "account_id", "shortcut_name", "display_name", "field_type", "required", "column_order", "options"];
var paramList = {'account_id': accountId};
if (showDeleted) {
columnList.push("archived_at");
}
_.extend(paramList, {'columns': columnList});
return callQuery(paramList);
}
};
Here is my test:
'use strict';
var assert = require('assert');
var sinon = require('sinon');
var Promise = require('bluebird');
var proxyquire = require('proxyquire');
var returnedValues = require('../../../return_values.js');
var fieldGateway = proxyquire('../../../../src/fields/lib/gateway', {});
describe('gateway', function () {
var accountId = 100;
var fieldId = 200;
var _query, sql, mockData, rows;
describe('createField', function() {
describe('is successful with a column order value', function () {
beforeEach(function() {
sql = 'INSERT into fields VALUES (:uuid, :accountId, :shortcutName, :displayName, :fieldType, :widgetType, :columnOrder, :options, :required, NULL)';
mockData = returnedValues.getFieldInputValues();
});
it("should only insert new field", function () {
_query = sinon.spy(function() { return Promise.resolve(); });
fieldGateway.initialize(_query);
fieldGateway.createField(fieldId, accountId, mockData);
mockData.accountId = accountId;
mockData.uuid = fieldId;
mockData.options = JSON.stringify(mockData.options);
assert.equal(sql, _query.getCall(0).args[0]);
assert.deepEqual(mockData, _query.getCall(0).args[1]);
});
it.only("_query should be called with the right sql statement and parameterList", function () {
_query = sinon.stub().returns(Promise.resolve(fieldId));
// _query.onCall(0).returns(Promise.resolve([{'MAX(column_order)': 10}]));
// _query.onCall(1).returns(Promise.resolve(fieldId));
fieldGateway.initialize(_query);
delete mockData.columnOrder;
fieldGateway.createField(fieldId, accountId, mockData);
console.log(_query.args);
assert.equal(sql, _query.getCall(0).args[0]);
fieldGateway.createField.restore();
});
});
});
});
The problem is that when the test runs, the only SQL query that runs is the SELECT statement. What should happen is one SQL statement runs, then an INSERT statement runs
This happens because bluebird is a true Promise/A+ compliant library. And by definition all chained promises must be run in a different execution tick. So only the first promise is executed synchronously (in same tick).
You should tell mocha to "wait" for the rest to act. You do this by specifying a done callback in your unit test and calling it accordingly when your promises finished their job
it.only("_query should be called with the right sql statement and parameterList", function (done) {
_query = sinon.stub().returns(Promise.resolve(fieldId));
fieldGateway.initialize(_query);
delete mockData.columnOrder;
fieldGateway.createField(fieldId, accountId, mockData)
.then(function(){
/// assertion code should be adjusted here
console.log(_query.args);
assert.equal(sql, _query.getCall(0).args[0]);
fieldGateway.createField.restore();
//tell Mocha we're done, it can stop waiting
done();
})
.catch(function(error) {
//in case promise chain was rejected unexpectedly
//gracefully fail the test
done(error);
};
});
Whenever you test your promise-returning functions you should always handle result in a .then

Categories