Serverless querying NodeJS MSSQL and cannot get result back to callback - javascript

I'm writing a small Serverless function to query a MSSQL db using the node mssql library (https://www.npmjs.com/package/mssql#callbacks)
I've read the documentation and I think I'm doing everything right but getting confused - I can see the result in my logs, but the main function callback is not being called and therefore data not being outputted by the API (basically the whole thing times out)
Heres my Lambda function:
import {success, error} from './libs/response-lib';
import {EPDBConfig} from "./libs/Database-lib";
import sql from "mssql";
import config from "./config";
export function main(event, context, callback) {
console.log("start");
EPDBConfig().then(dbConfig => {
if(config.debug) console.log("Hello!");
let EPDBconfig = {
user: dbConfig.dbuser,
password: dbConfig.dbpassword,
server: dbConfig.dbhost,
database: dbConfig.dbname
};
sql.connect(EPDBconfig)
.then(pool => {
return pool.request()
.input('student_no', sql.Int, 129546)
.query('select * from Student where StudentNo = #student_no')
}).then(result => {
console.log("success!");
if(config.debug) console.log('result', result);
return result;
}).catch(err => {
if(config.debug) console.log('err1', err);
return err;
});
sql.on('error', err => {
if(config.debug) console.log('err2', err);
return callback(null, error(err));
});
sql.on('done', result => {
if(config.debug) console.log('done', result);
return callback(null, success(result));
});
}).catch(err => {
if(config.debug) console.log('err3', err);
return callback(null, error(err));
})
}
DB Config is pulled from AWS KMS for secure vars
import AWS from "aws-sdk";
import config from "../config";
const kms = new AWS.KMS({
region: AWS.config.region
});
export function EPDBConfig() {
//DECRYPT THE DATABASE CONNECTION DETAILS
return new Promise((resolve, reject) => {
let params = {
CiphertextBlob: Buffer(process.env.epdb, 'base64')
};
kms.decrypt(params, function(err, data) {
if (err) {
reject(err);
} // an error occurred
else {
let dbParams = JSON.parse(String(data.Plaintext));
resolve(dbParams);
}
});
});
}
and response lib:
export function success(data, message) {
return buildResponse(200, true, data, message);
}
export function error(data, message) {
return buildResponse(400, false, data, message);
}
export function unauthorized(data, message) {
return buildResponse(401, false, data, message);
}
export function forbidden(data, message) {
return buildResponse(403, false, data, message);
}
export function exception(data, message) {
return buildResponse(500, false, data, message);
}
function buildResponse(statusCode, successState, data, message) {
var body = {
success: successState,
message: message
};
if (successState) {
body.data = data;
}
return {
statusCode: statusCode,
headers: {
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Credentials': true
},
body: JSON.stringify(body)
};
}
Can anyone point out where I'm going wrong here? I think I have a whole pile of promises going on. The sql.on('done', result => { ... doesn't appear to work, and I tried adding 'return callback(null, success(result));' in the area where I have 'success'
Please help me!

So, I endded up resolving this with a bit of refactoring:
import sql from "mssql";
import _ from "lodash";
import {success, error} from './libs/response-lib';
import {DB} from "./libs/Database-lib";
import {Student} from "./models/ep.student";
export function main(event, context, callback) {
DB().then(pool => {
return new Promise((resolve, reject) => {
pool.connect(err => {
if(err) reject(err);
pool.request()
.input('StudentNo', sql.Int, 129546)
.execute('StoredProcedure')
.then(result => {
if(process.env.debug) console.log('result', result);
let student = new Student();
_.assign(student, result.recordset[0]);
resolve(student);
pool.close();
}).catch(err => {
reject(err);
pool.close();
});
});
});
}).then(result => {
if(process.env.debug) console.log('result', result);
callback(null, success(result));
}).catch(err => {
if(process.env.debug) console.log('err', err);
callback(null, error(err));
});
}
The first issue was that I wasn't terminating my connection - just a note that I switched to using a stored procedure as this was always going to be the case.
The second issue was that I wasn't really using promises correctly (i think) I wrapped up my connection in a promise and only when I had the response did I resolve or reject it.

Related

unable to catch any form of error or response from firebase notification callback function in Node js

I am using the package "fcm-node" in order to send notifications to certain device id.
the sendNotification function is as follows:
const FCM = require('fcm-node');
const serverKey = process.env.SERVER_KEY;
const fcm = new FCM(serverKey);
function sendNotification(registrationToken, title, body, type, key) {
const message = {
to: registrationToken,
collapse_key: key,
notification: {
title: title,
body: body,
delivery_receipt_requested: true,
sound: `ping.aiff`
},
data: {
type: type,
my_key: key,
}
};
fcm.send(message, function (err, value) {
if (err) {
console.log(err);
return false;
} else {
console.log(value);
return value;
}
});
};
module.exports = {
sendNotification
};
The api function I use to call this function is as follows:
router.get('/test', async (req, res, next) => {
const promise = new Promise((resolve, reject) => {
let data = sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
console.log(data)
if (data == false) reject(data);
else resolve(data);
});
promise
.then((data) => { return res.status(200).send(data); })
.catch((data) => { return res.status(500).send(data) })
});
When I console.log the "err" and "value" from the sendNotification, I get either of the followings:
{"multicast_id":4488027446433525506,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1652082785265643%557c6f39557c6f39"}]};
{"multicast_id":8241007545302148303,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
In case it is successful, I made sure that the device is receiving the notification.
The problem is in the api's data. It is always "undefined" and weither send notification is successful or not I get the 200 Ok status.
What seems to be the problem?
You can't return anything from the function (err, value) {} callback of a node-style asynchrnous function.
Your sendNotification() function needs to return a promise. util.promisify() makes the conversion from a node-style asynchronous function to a promise-returning asynchronous function convenient. Note the return, it's important:
const FCM = require('fcm-node');
const serverKey = process.env.SERVER_KEY;
const fcm = new FCM(serverKey);
const { promisify } = require('util');
fcm.sendAsync = promisify(fcm.send);
function sendNotification(registrationToken, title, body, type, key) {
return fcm.sendAsync({
to: registrationToken,
collapse_key: key,
notification: {
title: title,
body: body,
delivery_receipt_requested: true,
sound: `ping.aiff`
},
data: {
type: type,
my_key: key,
}
});
}
module.exports = {
sendNotification
};
Now you can do what you had in mind
router.get('/test', async (req, res, next) => {
try {
const data = await sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
return res.status(200).send(data);
} catch (err) {
return res.status(500).send(err);
}
});
Maybe it will help, at first try to return your response (the promise) in sendNotification, as actually you have a void function, that's why it's always undefined and after in your route
router.get('/test', async (req, res, next) => {
try {
const data = sendNotification('', 'dfsa', 'asds', 'dfas', 'afsdf');
if (data) {
return res.status(200).send(data);
}
} catch(err) {
return res.status(500).send(err);
}
});

Save responses to multiple GET requests in a single local json file - node.js

The problem:
I have a function that maps over countries and regions and creates an array of urls, then makes a GET request to each one. I want to save the responses in a single json file, and I want this function to handle that as well.
Expected results:
I expected to be able to run the function as needed (like when source data is updated), and get a new or updated local json file with all the data objects in one array.
Actual results:
A file with only one record, an array with the last response object.
What I've tried:
I tried using fs.writeFile and fs.readFile. I did not get any errors, but the resulting file had only one record, even though console showed all the requests being made. It seemed that each response was being written over the previous.
Minimum reproducable (node.js) example:
const fs = require('fs')
// subset of countries and codes for demo purposes
const countryDirList = [
'antarctica',
'central-asia',
]
const fbCountryCodes = [
{ "region": "antarctica", "codes": ["ay", "bv"] },
{ "region": "central-asia", "codes": ["kg", "kz"] },
]
const callingUrlsInSequence = async () => {
fs.writeFile('./test.json', '[]', function (err) {
if (err) throw err
console.log('File - test.json - was created successfully.')
})
try {
const urlConstructor = countryDirList.map(async (country) => {
console.log('countries mapped', country)
return fbCountryCodes.filter(async (f) => {
if (country === f.region) {
const urls = f.codes.map(async (c) => {
const response = await axios({
method: 'get',
url: `https://raw.githubusercontent.com/factbook/factbook.json/master/${country}/${c}.json`,
responseType: 'json',
headers: {
'Content-Type': 'application/json',
},
})
fs.readFile('./test.json', function (err, data) {
let json = JSON.parse(data)
json.push(response.data)
setTimeout(() => {
fs.writeFile('./test.json', JSON.stringify(json), function (err) {
if (err) throw err
console.log('The "data to append" was appended to file!')
})
}, 1000)
})
return response.data
})
const dataArr = await Promise.all(urls)
dataArr.map((item) =>
console.log(
'dataArr',
item.Government['Country name']['conventional short form']
)
)
}
})
})
} catch (err) {
console.log('axios error: ', err)
}
}
callingUrlsInSequence()
I'm re-writing this question now because it kept getting downvoted, and I could see that it was not very concise.
I can also see now, that obviously, the fs.readFile inside the fs.writeFile is not going to work in the code I provided, but I'm leaving it there in case it might help someone else, combined with the solution I provided in response to my own question.
I ended up learning how to solve this problem with both node-fetch and axios. They are not exactly the same.
For both:
First, check for existence of destination file, and create one if it's not already there.
const createNew = () => {
try {
if (existsSync('./data.json')) {
console.log('file exists')
return
} else {
writeFile('./data.json', '[]', (error, data) => {
if (error) {
console.log('fs.writeFile - create new - error: ', error)
return
}
})
}
} catch (err) {
console.log('fs.existsSync error: ', err)
}
}
createNew()
Then make the array of urls:
const countryDirList = [...countries]
const fbCountryCodes = [...codes]
const urls = []
// maybe a reducer function would be better, but my map + filter game is much stronger X-D
const makeUrls = (countriesArr, codesArr) =>
countriesArr.map((country) => {
return codesArr.filter((f) => {
if (country === f.region) {
return f.codes.map((code) => {
return urls.push(
`https://raw.githubusercontent.com/factbook/factbook.json/master/${country}/${code}.json`
)
})
}
})
})
makeUrls(countryDirList, fbCountryCodes)
Next, make the requests.
Axios:
fs.readFile('./data.json', (error, data) => {
if (error) {
console.log(error)
return
}
Promise.all(
urls.map(async (url) => {
let response
try {
response = await axios.get(url)
} catch (err) {
console.log('axios error: ', err)
return err
}
return response
})
)
.then((res) => {
const responses = res.map((r) => r.data)
fs.writeFile('./data.json', JSON.stringify(responses, null, 2), (err) => {
if (err) {
console.log('Failed to write data')
return
}
console.log('Updated data file successfully')
})
})
.catch((err) => {
console.log('axios error: ', err)
})
})
Node-fetch:
//same basic structure, readFile with fetch and write file inside
fs.readFile('./data2.json', (error, data) => {
if (error) {
console.log(error)
return
}
async function fetchAll() {
const results = await Promise.all(
urls.map((url) => fetch(url).then((r) => r.json()))
)
fs.writeFile('./data2.json', JSON.stringify(results, null, 2), (err) => {
if (err) {
console.log('Failed to write data')
return
}
console.log('Updated data file successfully')
})
}
fetchAll()
})
Both methods produce exactly the same output: a json file containing a single array with however many response objects in it.

Node/Express return error to main function?

I'm in a situation where I have a POST route that calls a function multiple times. I want the request to return an error if the called function returns an error, but I am not sure how to achieve this. See this image:
This is my code:
function POSTcord(lat, lng) {
axios
.post(process.env.SOS_POST_URL + process.env.SOS_POST_CODE, {
batteryLevel: 100,
longitude: lng,
latitude: lat
})
.then(res => {
console.log(`statusCode: ${res.status}`)
})
.catch(error => {
console.error(error.message);
})
}
router.post('/test', async (req, res) => {
let passedCords = req.body;
try {
for (const cord of passedCords) {
POSTcord(cord.lat, cord.lng);
}
res.status(200).json({status:"success", message: "hello!"});
} catch (err) {
console.error(err.message);
res.status(500).send("Server error");
}
});
I want the route /test to return an error if the function POSTcord catches an error somewhere in the loop. Any ideas on this? I'm thinking I could pass res to POSTcord function, but that didn't work. Thankful for any input :)
You need to return the Promise and make sure the error is thrown/rejected:
Either do this:
function POSTcord(lat, lng) {
return axios // <--------------- THIS IS VERY IMPORTANT
.post(process.env.SOS_POST_URL + process.env.SOS_POST_CODE, {
batteryLevel: 100,
longitude: lng,
latitude: lat
})
.then(res => {
console.log(`statusCode: ${res.status}`)
})
.catch(error => {
console.error(error.message);
throw error; // <----------- ALSO DO THIS
})
}
Or do this:
function POSTcord(lat, lng) {
return axios // <--------------- THIS IS VERY IMPORTANT
.post(process.env.SOS_POST_URL + process.env.SOS_POST_CODE, {
batteryLevel: 100,
longitude: lng,
latitude: lat
})
.then(res => {
console.log(`statusCode: ${res.status}`)
})
// DON'T CATCH THE ERROR!!
}
Then all you need to do is await to get the error:
router.post('/test', async (req, res) => {
let passedCords = req.body;
try {
for (const cord of passedCords) {
await POSTcord(cord.lat, cord.lng); // DO THIS FOR CATCH TO WORK
}
res.status(200).json({status:"success", message: "hello!"});
} catch (err) {
console.error(err.message);
res.status(500).send("Server error");
}
});
If you want to call POSTcord() in parallel you can await using Promise.all():
router.post('/test', async (req, res) => {
let passedCords = req.body;
try {
let promises = [];
for (const cord of passedCords) {
let p = POSTcord(cord.lat, cord.lng);
promises.push(p);
}
await Promise.all(promises); // DO THIS FOR CATCH TO WORK
res.status(200).json({status:"success", message: "hello!"});
} catch (err) {
console.error(err.message);
res.status(500).send("Server error");
}
});

Is there a correct way to handle promisified stream error in Nodejs

I am trying to catch an error in the controller and send status(500) to the front-end to let the user know that there is a streaming error. But for some reason the error is not caught and I am sending status(200) to the user. Let me know if i am doing something wrong.
file - utils.js
import WebSocket from 'ws';
import Twitter from 'twitter-lite';
import ck from 'ckey';
export const stream = (term, clients, twitterStream) => {
try {
const twitter = new Twitter({
// subdomain: 'api', // "api" is the default (change for other subdomains)
// version: '1.1', // version "1.1" is the default (change for other subdomains)
version: '2', // version "1.1" is the default (change for v2)
extension: false, // true is the default (this must be set to false for v2 endpoints)
consumer_key: ck.TWITTER_CONSUMER_KEY,
consumer_secret: ck.TWITTER_CONSUMER_SECRET,
access_token_key: ck.TWITTER_ACCESS_TOKEN_KEY,
access_token_secret: ck.TWITTER_ACCESS_TOKEN_SECRET,
});
let stream = twitter.stream('statuses/filter', { track: term });
new Promise(function (resolve, reject) {
stream.on('data', function (tweet) {
console.log('tweet');
resolve(broadcast(clients, JSON.stringify(tweet)));
});
stream.on('error', function (error) {
reject(error);
});
}).catch(function (e) {
console.log('stream error catch: ', e);
// throw e;
});
twitterStream = stream;
return twitterStream;
} catch (error) {
console.log('error from util', error);
// throw error;
}
};
const broadcast = (clients, message) => {
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
};
controller
import { stream } from './utils.js';
let twitterStream;
// Sets search term for twitter stream.
export const setSearchTerm = (req, res) => {
try {
const { term } = req.params;
console.log('setSearchTerm');
console.log('term: ', term);
if (twitterStream) {
console.log('getTweetPause');
twitterStream.destroy();
}
twitterStream = stream(term, req.app.locals.clients, twitterStream);
res.status(200).json({ message: 'Successful search request' });
} catch (error) {
res.status(500).json({ message: error });
}
};
file - utils.js
import WebSocket from 'ws';
import Twitter from 'twitter-lite';
import ck from 'ckey';
export const stream = (term) => {
const twitter = new Twitter({
// subdomain: 'api', // "api" is the default (change for other subdomains)
// version: '1.1', // version "1.1" is the default (change for other subdomains)
version: '2', // version "1.1" is the default (change for v2)
extension: false, // true is the default (this must be set to false for v2 endpoints)
consumer_key: ck.TWITTER_CONSUMER_KEY,
consumer_secret: ck.TWITTER_CONSUMER_SECRET,
access_token_key: ck.TWITTER_ACCESS_TOKEN_KEY,
access_token_secret: ck.TWITTER_ACCESS_TOKEN_SECRET,
});
let stream = twitter.stream('statuses/filter', { track: term });
return stream;
};
export const broadcast = (clients, message) => {
clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
};
controller
import { stream, broadcast } from './utils.js';
let twitterStream;
// Sets search term for twitter stream.
export const setSearchTerm = async (req, res) => {
try {
const { term } = req.params;
console.log('setSearchTerm');
console.log('term: ', term);
if (twitterStream) {
console.log('getTweetPause');
twitterStream.destroy();
}
const currentStream = stream(term);
twitterStream = currentStream;
await new Promise((resolve, reject) => {
currentStream.on('data', function (tweet) {
console.log('tweets: ');
broadcast(req.app.locals.clients, JSON.stringify(tweet));
resolve(tweet);
});
currentStream.on('error', function (error) {
reject(error);
});
});
res.status(200).json({ message: 'Successful HTTP request' });
} catch (error) {
console.log('error catch: ');
res.status(500).json({ message: error });
}
};

Better error handling with Promises?

I am currently experimenting Google Firebase functions to access Google APIs. It's running fine, but I am a little bit lost in trying to manage the errors that could be detected ...
In the .HTTPS getGoogleUsers functions , I would like to return an HTTP status code ( 200 or error code ) , and the data ( or error message )
As far as I can see , I can get errors:
from the connect() function ( 500: Internal server error or 401 Unauthorized )
from the listUsers() function ( 500: Internal server error or 400 Bad Request )
Am I totally or partially wrong ? what should be my strategy in this case ?
thanks for feedback ..
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const {google} = require('googleapis');
const KEY = require('./service-key.json');
// Create JSON Web Token Client
function connect () {
return new Promise((resolve, reject) => {
const jwtClient = new google.auth.JWT(
KEY.client_email,
null,
KEY.private_key,
['https://www.googleapis.com/auth/admin.directory.user'],
'adminuser#mydomain.com'
);
jwtClient.authorize((err) => {
if(err) {
reject(err);
} else {
resolve(jwtClient);
}
});
});
}
function listUsers (client) {
return new Promise((resolve, reject) => {
google.admin('directory_v1').users.list({
auth: client,
domain: 'mydomain.com',
}, (err, response) => {
if (err) {
reject(err);
}
resolve(response.data.users);
});
});
}
function getAllUsers () {
connect()
.then(client => {
return listUsers(client);
})
.catch(error => {
return error;
})
}
exports.getGoogleUsers = functions.https.onRequest((req, res) => {
const users = getAllUsers();
if (error) {
status = error.status;
data = error.message;
} else {
status = 200;
data = users;
}
res.send({ status: status, datas: data })
});
I think you are looking for
function getAllUsers () {
return connect().then(listUsers);
//^^^^^^
}
exports.getGoogleUsers = functions.https.onRequest((req, res) => {
getAllUsers().then(users => {
return {status: 200, datas: users};
}, error => {
return {status: error.status, datas: error.message};
}).then(response => {
res.send(response);
});
});
This uses the .then(…, …) method with two callbacks to distinguish between success and error case, and to wait for the result of the promise.

Categories

Resources