Consider the following example Lambda function:
//get-account
exports.handler = (data, context, callback) => {
callback("Account not found");
};
Lambda would output this error like so:
{
"errorMessage": "Account not found"
}
This is exactly what I want. However, consider then calling this function via the AWS API
return new Promise((resolve, reject) => {
Lambda.invoke({
FunctionName: 'get-account',
InvocationType: "RequestResponse",
Payload: JSON.stringify({ account_id: account_id })
}, function(err, data) {
//only true if the invocation failed...
if(err) { return reject(err); }
//parse the payload. We will need it regardless of error/success
let response = JSON.parse(data.Payload);
//did the function throw an error?
if(data.FunctionError) {
//the error message will be [object Object], which is no good!
reject(response.errorMessage);
return;
}
//success!
resolve(response);
});
});
In this case, I find the error is always converted to the [object Object] string. Lambda appears to take the error object from the callback (the error object it creates) and then wraps it in another error object. So ultimately Lambda.invoke is doing this:
{
errorMessage: {
errorMessage: "Account not found"
}
}
But instead of returning this built object, it returns,
{
errorMessage: '[object Object]'
}
Anyone see a way around this? I do NOT want to change how my Lambda function outputs errors. I only want to get the correct error from the Lambda invoke function. Is this simply not possible due to how Lambda.invoke() wraps the error again?
Related
I have a Flutter app and I'm trying to get a client nonce from braintree. Per the braintree documentation, I have this in my cloud function:
exports.getClientNonce = functions.https.onCall(async (data, context) => {
gateway.clientToken.generate({}, function (err, response) {
if (err) {
throw new functions.https.HttpsError('unknown', 'Error getting client nonce');
} else {
console.log(`token: ${response.clientToken}`);
return response.clientToken;
}
});
});
Then, in my Flutter app I call the function (again, I'm following what the plugin says):
try {
HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'getClientNonce',
);
dynamic result = await callable.call({});
final value = result.data;
debugPrint('token: $value');
var data = await BraintreePayment().showDropIn(
nonce: value,
amount: '2.0',
enableGooglePay: false,
inSandbox: true);
print("Response of the payment $data");
} on CloudFunctionsException catch (e) {
debugPrint('An error occurred');
} catch (e) {
debugPrint('An error occurred');
}
}
I tried changing the cloud function so that it only returns a random number (as soon as the function is executed), and my Flutter app is correctly receiving the value (so the cloud function is communicating fine). And in my Firebase console, I am able to view the client nonce specified by console.log. But the function is for whatever reason unable to return the actual client nonce. (It should be should be some string hash that is >2000 characters long)
The callable function needs to return a promise from the top-level of the function callback that resolves with the value to return. Right now, you're returning nothing from the top-level. The return you have now is just returning a value from the inner callback function that you pass to braintree API. This isn't going to propagate to the top level.
What you need to do is either use a version of braintree API that returns an API (if one exists), or promisify the existing call that uses a callback.
See also "3. Node style callback" here: How do I convert an existing callback API to promises?
I have not tested this, but the general format if you apply that pattern will look more like this:
exports.getClientNonce = functions.https.onCall(async (data, context) => {
return new Promise((resolve, reject) => {
gateway.clientToken.generate({}, function (err, response) {
if (err) {
reject(new functions.https.HttpsError('unknown', 'Error getting client nonce'));
} else {
console.log(`token: ${response.clientToken}`);
resolve(response.clientToken);
}
});
});
});
How can i trigger error object in lambda.invoke
lambda.invoke(params, (err, data) => {
if (err) {
reject(.... // I would have expected below error to show up here
else
// error shows up inside the data.Payload
const result = data.Payload
// I have to create a condition to check for the error
resolve(result);
in the called lambda, i've tried the following:
exports.handler = ( event, context, callback) => {
if (payload === '')
context.done(new Error('my error message');
}
however, the error object ends up in the payload, where I have to check for it instead of going into a catch or other error path.
Instead of using the "older way" to stop execution you should use the callback(error, [success]) method. So in your lambda being invoked try callback('my error message') and that should go into your if block. You can read the documentation here for more info. I believe that in the "older way" of doing things context.done() is considered successful and context.fail() was used to signify an error.
Similar issues have been posted, but none quite match what I've run into. I'm doing a simple POST to an internal server to get back product data. The call is successful and I see the JSON data correctly logging to my terminal when I do a console.log on the server side. The issue arises on the client side, when in the callback, the result and error both are undefined.
Server:
Meteor.methods({
ProductSearch: function(searchTerm) {
var method = 'POST';
var url = 'server';
var options = {
headers:{"content-type":"application/json"},
data: {
query:"trees"
}
};
return HTTP.call(method, url, options, function (error, result) {
if (error) {
console.log("ERROR: ", result.statusCode, result.content);
} else {
var txt = JSON.parse(result.content);
console.log("SUCCESS: Found "+txt.totalResults+" products");
}
});
}
});
Client:
Meteor.call('ProductSearch', searchTerm, function (error, result) {
if (error) {
console.log("error occured on receiving data on server. ", error );
} else {
var respJson = JSON.parse(result.content);
Session.set("productSearchResults", respJson);
}
});
When I log the values of error, and result on callback, they are both undefined, and I get the following error: Exception in delivering result of invoking 'ProductSearch': TypeError: Cannot read property 'content' of undefined
In your server-side method, you're not correctly returning the result of the HTTP.call, since you're using the asynchronous version, HTTP.call will return undefined and the result will only be accessible in the callback.
Use the synchronous version of HTTP.call instead and you'll be fine.
try{
var result = HTTP.call(method, url, options);
return JSON.parse(result.content);
}
catch(exception){
console.log(exception);
}
See the corresponding docs for HTTP.call for additional information.
asyncCallback Function
Optional callback. If passed, the method runs
asynchronously, instead of synchronously, and calls asyncCallback. On
the client, this callback is required.
https://docs.meteor.com/#/full/http_call
I have a below Restify custom error that is being thrown to my catch block of BlueBird Promise.
var test = function() {
respObject = { hello: { world: 'aasas' } };
throw new restify.errors.ServiceError(respObject, 422);
}
Then in ServiceError:
function ServiceError(respObject, statusCode) {
restify.RestError.call(this, {
restCode: 'ApiError',
statusCode, statusCode,
message: 'Api Error Occurred',
constructorOpt: ServiceError,
body: {
message: 'Api Error Occurrede',
errors: respObject.toJSON()
}
});
this.name = 'CustomApiError';
}
util.inherits(ServiceError, restify.RestError);
restify.errors.ServiceError = ServiceError;
However on the calling function of test():
test().catch(function(err) {
console.log(err);
});
It's returning undefined is not a function. Is there a reason why it's not returning err object to above calling function under catch block?
The problem isn't with Restify, it's with your test function. You're calling test().catch, but test() doesn't return anything—i.e. it returns undefined. You're essentially calling undefined.catch, which doesn't exist.
If you want to call catch on the result of test, it needs to return a promise.
I found the answer. It was respObject.toJSON() that was causing undefined is not a function because respObject does not have toJSON() function :)
The question:
I'm using Chai to do the tests and I seem to be stuck on testing an expected error:
Chai expected [Function] to throw an (error)
Current code:
Here's the code of the test:
describe('Do something', function () {
it('should remove a record from the table', function (done) {
storage.delete(ID, done);
});
it('should throw an error when the lookup fails', function () {
expect(storage.delete.bind(storage, ID)).to.throw('Record not found');
});
});
Here's the code of the function:
delete: function (id, callback) {
// Generate a Visitor object
visitor = new Visitor(id);
/* Delete the visitor that matches the queue an
cookie provided. */
tableService.deleteEntity(function (error, response) {
// If successful, go on.
if (!error) {
// Do something on success.
}
// If unsuccessful, log error.
else {
if (error.code === 'ResourceNotFound') {
throw new Error('Record not found');
}
// For unexpected errros.
else {
throw new Error('Table service error (delete): ' + error);
}
}
if (callback) callback();
});
},
Attempted solutions:
I've tried multiple variations of calling expect function (including calling anonymous function:
expect(function() {storage.delete(ID);}).to.throw('Record not found');
Bind, as provided in the example,
and the basic one of
expect(storage.delete(ID)).to.throw('Record not found');
I've also tried substituting the throw parameter from 'Record not found' to multiple things including directing the input to an already created error (Error), and creating a new error in the parameter (new Error('Record not found'));
Possible causes:
I have a suspicion that the error is not being thrown because it takes a while for the test to communicate with the database to delete the record, however I am not sure of how I could remedy that.
Additionally, it seems that the test that runs right after this one actually returns the error that was supposed to be returned on THIS test.
Given (from comments) that tableService.deleteEntity is asynchronous, it is impossible to test that throw. And the code itself is invalid. Because the thrown exception won't be caught, it will be unhandled as it was thrown in a different tick. Read more about Asynchronous error handling in JavaScript and unhandled exceptions in Node.js
In other words such a function cannot be tested for throwing errors:
function behaveBad(){
setTimeout(function(){
throw new Error('Bad. Don\'t do this');
}, 50);
}