I'm using aws-sdk module in node.js, and am attempting to validate a config object which contains a profile parameter (sent as a string and used to construct a SharedIniFileCredentials object) and a region.
To validate the sent region, I will create an ec2 object using a known region, and the profile parameter provided above and then check if the region paramter is found in the result of ec2.describeRegions.
I'd like to validate the profile parameter by catching any exceptions thrown executing the above, and am trying this with the setup below:
var aws = require('aws-sdk'); // aws sdk
var creds = new aws.SharedIniFileCredentials({profile: profile});
var conf = new aws.Config({
"credentials": creds,
"region": "eu-west-1" //Known region
});
var ec2 = new aws.EC2(conf);
try {
ec2.describeRegions({}, function (err, data) {
if (err) throw new Error(err.message);
//Do stuff below to check if region is found in data.Regions array
//...
// If not found -> errorCallback();
successCallback();
});
} catch(err){
errorCallback();
}
It seems that when the profile is invalid, err.message is correctly coming out as:
CredentialsError: Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1
but instead of catching the error constructed from this, the error is unhandled and kills the process:
C:\workspaces\njw\node_modules\aws-sdk\lib\request.js:31
throw err;
^
Error: Missing credentials in config, if using AWS_CONFIG_FILE, set
AWS_SDK_LOAD_CONFIG=1
at Response. (C:\workspaces\njw\test.js:69:24)
at Request. (C:\workspaces\njw\node_modules\aws-sdk\lib\request.js:364:18)
If I don't throw the error inside the describeRegions callback, it will be correctly handled by the catch clause. Any idea why this isn't working inside the callback?
The problem here is the scope of try/catch block.
Apart from successCallback and errorCallback are not defined in your example and assuming they are valid in our scope and their meaning are the callbacks of a function, this block
try {
ec2.describeRegions({}, function (err, data) {
if (err) throw new Error(err.message);
successCallback();
});
} catch(err){
errorCallback();
}
is equivalent to this one
function responseHandler(err, data) {
if (err) throw new Error(err.message);
successCallback();
}
try {
ec2.describeRegions({}, responseHandler);
} catch(err){
errorCallback();
}
and the thrown Error is not in not in a try/catch block.
Errors returned from asynchronous function accepting callback (this is not a aws-sdk specific) usually are handled returning the error to caller callback, something like:
function myAsyncCheck(done) {
ec2.describeRegions({}, (err, data) => {
if (err) return done(err);
let result, something_bad;
//Do checks for something bad accordingly the logic you need
if(something_bad) return done(new Error("Something bad"));
// Do other stuff to set result
done(null, result);
});
});
or, if you need to call ec2.describeRegions in global scope and successCallback and errorCallback are functions in global scope, probably you need to change errorCallback in order to accept an error as parameter, otherwise you can't get error details, but this is really an ugly design pattern.
var ec2 = new aws.EC2(conf);
ec2.describeRegions({}, (err, data) => {
if (err) return errorCallback(err);
//Do checks for something bad accordingly the logic you need
if(something_bad) return errorCallback(new Error("Something bad"));
successCallback();
});
Hope this helps.
Related
This bit of code, after connecting, does some stuff
controller.connect((response)=>{ does some stuff })
Down deep in the guts of the connect method this async function gets called, which returns a promise by way of the callback
async function ServerSend(endpoint,params,callback) {
const response = axios.get(host+endpoint, {params})
callback(response);
}
If the server is not available it correctly throws: UnhandledPromiseRejectionWarning: Error: connect ECONNREFUSED 127.0.0.1:8088
What is the correct way to handle this exception? I could possibly add a catch in the async method and rewrite all the call backs to return an err. But I'd rather catch it at the caller. I have not been able to get either method to work.
axios.get(host+endpoint, {params}) // this is a promise
so if it resolves it will be ok, but if it rejects (and yuou dont have any try .. catch, any .catch attached - it will throw error that exception is unhandled.
Why way would be to:
async function ServerSend(endpoint,params,callback) {
try {
const response = await axios.get(host+endpoint, {params})
callback(null, response);
} catch (err) {
callback(err, null);
}
}
OR
function ServerSend(endpoint,params,callback) {
// most callbacks are two parameters - 1st erro or null, second data if no error is present.
axios.get(host+endpoint, {params}).then(data => callback(null, data)).catch(err => callback(err, null));
}
the answer to this question: How to get node to exit when mongo connect fails contains async/wait code for a connection
however, my code (running on node v11.5.0 and mongodb v3.1.13) is failing to catch:
(async function() {
let db;
try {
db = await MongoClient.connect(uri, { useNewUrlParser: true });
console.log("RETURN", db);
} catch (err) {
console.log('EXITING');
process.exit(1);
}
}());
to prove the point I intentionally give a uri without credentials:
mongodb://undefined#cluster0-shard-00-00-z4j9e.azure.mongodb.net:27017,cluster0-shard-00-01-z4j9e.azure.mongodb.net:27017,cluster0-shard-00-02-z4j9e.azure.mongodb.net:27017/test?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin&retryWrites=true
and what I get is output like this:
/Users/ekkis/dev/mongo/node_modules/mongodb/lib/topologies/replset.js:346
throw err;
^
MongoError: password must be a string
at passwordDigest (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/auth/scram.js:63:43)
at ScramSHA1.ScramSHA.auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/auth/scram.js:175:25)
at authenticate (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:232:17)
at authenticateLiveConnections (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:819:7)
at /Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:864:5
at waitForLogout (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:855:34)
at Pool.auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/connection/pool.js:862:3)
at Server.auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/topologies/server.js:931:20)
at auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/topologies/replset.js:1474:19)
at ReplSet.auth (/Users/ekkis/dev/mongo/node_modules/mongodb-core/lib/topologies/replset.js:1492:5)
so if the error had been caught, the console should have displayed the word 'EXITING', but does not. additionally, I contend an exception was thrown because otherwise the returned value would have been printed, which it was not
how can this be? what do I need to do to get it to work?
* Appendix I *
In fact, the promises version of this exhibits the same odd behaviour, it doesn't catch:
MongoClient
.connect(uri, { useNewUrlParser: true })
.then(dbc => {
console.log('SUCCESS');
})
.catch(err => {
console.log('EXITING');
process.exit(1);
});
and yes, I tested the callback version, which also suffers the same malady. Incidentally, passing an empty string for the uri works well. I don't get it
* Appendix II *
In fact, the problem seems to be particular to the credentials passed i.e. if I pass:
mongodb://x:y#cluster0-shard-[...]
I catch a "MongoError: authentication fail" as expected. passing:
mongodb://#cluster0-shard-[...]
interestingly returns a connection but credentials missing a ":" fail in this odd way, so:
mongodb://ekkis#cluster0-shard-[...]
fails to catch
Looks to me like it's a bug with however MongoClient is setting up its connections. You won't be able to use try & catch to handle asynchronously thrown errors within MongoClient code.
const {MongoClient} = require("mongodb");
process.on("uncaughtException", (err) => {
console.log("process err", err);
process.exit(1)
})
async function run () {
let db;
try {
// connection url will throw because password isn't provided
db = await MongoClient.connect("mongodb://myUsername:#localhost", { useNewUrlParser: true });
} catch (err) {
console.log('Exiting from thrown error', err);
process.exit(1);
}
}
run();
Here's a simplified example of what's happening -- the error will end up "uncaught" and caught by the uncaughtException handler
process.on("uncaughtException", (err) => console.log("uncaught", err));
try {
setTimeout(() => {
throw new Error("asynchronously thrown error");
})
} catch (err) {
console.log("Error will not be caught here")
}
When I was using mongo version 3.6.1, it was not an issue and i was able to handle the thrown exception using catch. But after a few days on another project this type of error occurred and was showing as the error thrown from
%project_folder%/node_modules/mongodb/lib/utils.js:668
(Don't mind about the slash in the path string.)
The mongodb version this time is 3.6.3. Upon checking the code in that file at the mentioned line I found the below piece of code. where the caught error is again being thrown.
fn(function(err, res) {
if (err != null) {
try {
callback(err);
} catch (error) {
return process.nextTick(() => {
throw error;
});
}
return;
}
callback(err, res);
});
I changed the throw error to console.error(error) and the problem got resolved. But still you need to be caught somewhere in our code where connect function is called.
I think this is because the above piece of code is checking for the presence of error and passing it to the callback function and then again throwing the same error again. I suppose it is the MongoDB driver developer community's responsibility to resolve this issue.
so on my first time learning AWS stuff (it is a beast), I'm trying to create e-mail templates, I have this lambda function:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
AWS.config.update({ region: "us-east-1" });
exports.handler = async (event, context, callback) => {
// Create createTemplate params
var params = {
Template: {
TemplateName: "notification" /* required */,
HtmlPart: "HTML_CONTENT",
SubjectPart: "SUBJECT_LINE",
TextPart: "sending emails with aws lambda"
}
};
// Create the promise and SES service object
const templatePromise = new AWS.SES({ apiVersion: "2010-12-01" })
.createTemplate(params)
.promise();
// Handle promise's fulfilled/rejected states
templatePromise
.then((data) => {
console.log(data);
callback(null, JSON.stringify(data) );
// also tried callback(null, data);
}, (err) => {
console.error(err, err.stack);
callback(JSON.stringify(err) );
});
as far as I am understanding, this function should return me a template? an object, anything? when I use the lambda test functionality I always got null in the request response
does anyone know what I am doing wrong here?
edit: and It is not creating the e-mail template, I check the SES Panel - email templates and it is empty
edit2: if I try to return a string eg: callback(null, "some success message"); it does return the string, so my guess is something wrong with the SES, but this function is exactly what we have in the AWS docs, so I assume it should just work..
Try not to resolve the Promise and change your code to just returning it as-is:
return await templatePromise;
which should present you some more detail of what is really going wrong in your code - it might be some hidden access issue - so you might need to adjust the role your lambda function is using. createTemplate on the other side should not return much in case of successful execution but just create the template.
Also try to follow the following try/catch pattern when using async (as described here in more detail: https://aws.amazon.com/de/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/)
exports.handler = async (event) => {
try {
data = await lambda.getAccountSettings().promise();
}
catch (err) {
console.log(err);
return err;
}
return data;
};
Im trying to download a file from S3 which doesn't exist on S3. I expect a error in this scenario and im also getting that error from aws-sdk i.e.
**/vagrant/node_modules/aws-sdk/lib/request.js:31
throw err;
^
NoSuchKey: The specified key does not exist.**
But the issue is, im not able to catch this error. If you check the code below, my listener request.on gets called and when i call reject in that the promise doesn't return from method downloadFontInfoFileFromS3 with reject.
Is there any way i can catch the error and gracefully reject the promise from downloadFontInfoFileFromS3 function?
downloadFontInfoFileFromS3(fileKey) {
return new Promise((resolve, reject) => {
const sThree = new awsSDK.S3();
const options = {
Bucket: awsConf.bucket,
Key: fileKey,
};
const downloadFilePath = SOME_PATH
const file = fs.createWriteStream(downloadFilePath);
const request = sThree.getObject(options);
const download = request
.createReadStream().pipe(file);
// called when error in aws-sdk
request.on('error', (error) => {
logger.error('Failed to download file from S3:', error.message);
reject(error);
});
download.on('error', (error) => {
reject(error);
});
download.on('finish', () => {
resolve(downloadFilePath);
});
// the synchronous code that we want to catch thrown errors on
});
}
I ran into this issue as well. Turns out I wasn't setting the error handler in the correct place.
Currently, you have:
// INCORRECT. Won't work
const download = request.createReadStream().pipe(file);
download.on('error', (error) => {
reject(error);
});
But you actually need to set the error handler on the stream, before you begin piping. Like this:
// Correct!
const download = request.createReadStream(); // notice the `.pipe()` has been removed
download.on('error', (error) => {
reject(error);
});
// now you can begin piping to the file
requestStream.pipe(file);
If you try setting the event handler after the pipe has begun, the error will "bypass" any try/catch or Promise .catch handlers you specify. Quite frustrating!
Check out this GitHub issue for another example.
const data = s3.getObject({ Bucket: process.env.AWS_BUCKET_NAME, Key: url }).createReadStream();
data.on("error", (err) => {
if (err)
return next(customError("Invalid Url", 400));
});
data.pipe(res);
Right Because Error is occourd when data in pipe.
I have an ExpressJS app where I have api.js in routes that manages connecting to Couchbase and then emits event couchbaseConnected that is awaited by init() function inside api.js.
Inside init() I want to push those exports.someFunction(req, res){return something;}. But when I just put these exports inside init() function, I get an error .get() requires callback functions but got a [object Undefined] so it seems like I am doing it wrong.
The question is how I can export functions from another function in NodeJS?
Here is the code:
//connecting to couchbase and emitting event on connection
couchbase.connect(dbConfiguration, function (err, bucket) {
if (err) {
console.log(err);
}
cb = bucket;
eventEmitter.emit('couchbaseConnected');
});
//listening for the event and fire init() when it's there
eventEmitter.on('couchbaseConnected', function (e) {
console.log('Connected to Couchbase.'.green);
init();
});
function init() {
exports.getUserData = function (req, res) {
if (req.user != undefined && req.user.meta != undefined) {
res.json(200, {result: 'ok'})
}
else {
res.json(401, {error: 'Unauthorized request.'})
}
};
}
Here is the ExpressJS .get() that is located in app.js:
app.get('/api/user/data/:type', api.getUserData);
Here is the ExpressJS .get() that is located in app.js:
app.get('/api/user/data/:type', api.getUserData);
The error message .get() requires callback functions but got a [object Undefined] makes quite clear what happens: You require the API module, it starts to connect to the db, you are defining your express app by passing a non-existent property to .get - which fails, since init has not yet been called and getUserData has not yet been assigned. What you need to do is
var api = require('api');
eventEmitter.on('couchbaseConnected', function () {
app.get('/api/user/data/:type', api.getUserData); // now it is available
});
However, this does not look like good code. Instead of loosely coupling them via that couchbaseConnected event you better should use explicit callbacks that are invoked with the requested values (i.e. the cb bucket, or the getUserData method). At least pass them as parameters to the emitted event.
Also, your setup is unconventional. I don't see why getUserData would need to be asynchronously defined - it should always be available. If the couchbase connection failed, I would not expect the /api/user/data/ service to not exist, but to respond with some 500 internal server error message.
This is an answer that I have made some assumptions as the data provided by you is not sufficient to know what you are doing when you start your app.
I would suggest some change in code:
module.exports.connect = function(callback){
couchbase.connect(dbConfiguration, function (err, bucket) {
if (err) {
console.log(err);
callback(err);
}
cb = bucket;
module.exports.getUserData = getUserData();
callback(err); //just callback with no error as you don't require to send the database
});
}
function getUserData(req, res){
if (req.user != undefined && req.user.meta != undefined) {
res.json(200, {result: 'ok'})
}
else {
res.json(401, {error: 'Unauthorized request.'})
}
};
and in your app.js file where you are starting the app just do this
var api = require('./api');
api.connect(function(error){
if (error) throw error;
app.listen(3000);
console.log('Express started on port 3000');
});
And then you can continue doing exactly what you were doing. It should work.
I have assumed that you are not explicitly calling the connect or its equivalent when you are starting the app.....So your getting that error.