Throwing errors/response outside exports.handler - node.js

I've just started using AWS Lambda/Node.JS/API Gateway with API Lambda Proxy integration, and I'm struggling with something that should be simple. Handling error from outside exports.handler.
I've developed all my code, it's all working fine, however, I'm struggling to handle errors. I'm using async/await, no callback.
Basically, my code looks like something like this:
exports.handler = async (event, context) => {
const sampleVar = await sampleFunction();
const response = {
statusCode: 200,
body: JSON.stringify(sampleVar),
isBase64Encoded: false
};
return response;
}
Great, right? My problem is that all my "real code" is being developed outside the exports.handler in different functions, something like:
async function sampleFunction() {
const test = 123;
return anotherFunction(test);
}
async function anotherFunction() {
const test2 = 'blablabla';
return test2;
}
Now, let's suppose that an error happens on my sampleFunction and I need to throw the error right away. I could just go throw { statusCode: 400, etc, etc };, however, if I throw the error, I get a "500 Internal Server Error" as my response, no matter what. I can only proper break my code if I handle the error from within my exports.handler function. In that case, I could go return { statusCode: 400, etc, etc }; or context.fail(), etc, and everything would be alright.
My question is: is there any way to "break" my code and send a proper response from outside my exports.handler? Or maybe externally call my exports.handler to return a specific response?

so, i would write this in the comments, but the markup is a bit poor. I would do something like the below:
exports.handler = async (event, context) => {
return await sampleFunction().then(craftOk).catch(craftErr);
}
function craftOk(res) {
return {
statusCode: 200,
body: JSON.stringify(res),
isBase64Encoded: false
};
}
function craftBad(err) {
return {
statusCode: 400,
body: err.message,
isBase64Encoded: false
}
}
for fun, I wrote a test script to validate return from catch on async/await...maybe it will help
function test(toggle) {
return (toggle) ? Promise.resolve("yay") : Promise.reject(new Error("boo"))
}
async function main1() {
let res = await test(true).then( res => {
return {
statusCode: 200,
body: res,
isBase64Encoded: false
}
})
.catch(err => {
return {
statusCode: 400,
body: err.message,
isBase64Encoded: false
}
})
return res
}
async function main2() {
let res = await test(false).then( res => {
return {
statusCode: 200,
body: res,
isBase64Encoded: false
}
})
.catch(err => {
return {
statusCode: 400,
body: err.message,
isBase64Encoded: false
}
})
return res
}
main1().then(console.log)
main2().then(console.log)

Related

How to correctly return error to user on error - Azure function

I am creating an Azure function that should return an error to the user when an exception is thrown. The issue I am having is that I can either throw the exception, or return something to the user, but not both.
With this code an exception is thrown, but nothing is returned to the user:
module.exports = async function (context, req) {
try {
null.toString()
} catch (e) {
response(context, 500, "message")
throw e
}
}
function response(context, statusCode, message) {
context.res = {
body: message,
status: statusCode
};
}
If I add in a context.done() then I can return a message to the user, but the exception no longer shows up:
module.exports = async function (context, req) {
try {
null.toString()
} catch (e) {
response(context, 500, "message")
throw e
}
}
function response(context, statusCode, message) {
context.res = {
body: message,
status: statusCode
};
context.done()
}
What is the correct way to do this?

Invoke lambda instances from lambda and wait for the longest to comple

I have a lambda and I want to invoke multiple lambdas instance and wait for the longest to complete.
I already checked online and found very interesting threads but none of them helped me achieve what I want.
Here's the code:
Lambda as promise
invokeVideoRenderer = (params) => {
const param = {
FunctionName: 'myFunction',
InvocationType: 'Event',
Payload: JSON.stringify({ body: params })
};
return new Promise(async (resolve, reject) => {
this.lambda.invoke(param, (error, data) => {
if (error) {
reject(error);
}
resolve(data); <--- it always returns { StatusCode: 202, Payload: '' }
});
});
};
the loop where I invoke the different lambdas
const promises = [...Array(totalNumberOfLambdaToInvoke).keys()].map(
async (part: number) => {
const body = {
// body
};
const request = await invokeVideoRenderer(body);
return request;
}
);
const results = await Promise.all(promises); <--- here I want to wait for the longest to complete
The lambda I am invoking:
const myFunction = async (event, context, callback) => {
try {
//...code
callback(null, {
status: 200,
body: JSON.stringify({
status: "SUCCESS",
}),
});
return;
} catch (error) {
callback(null, {
status: 500,
body: JSON.stringify({
status: "FAILED",
errorMessage: error,
}),
});
return;
}
};
What am I doing wrong?
An InvocationType of Event tells Lambda to invoke the child Lambda asynchronously. Hence the Lambda service responds with 202 (Accepted) response immediately. Meanwhile the child Lambda function executes.
If you want it to execute synchronously, then don't provide an InvocationType of Event. Either remove it entirely to get the default, or explicitly set it to RequestResponse (which is the default).
Note: it's typically not ideal to invoke Lambda functions synchronously if you can avoid it, because you are paying for both Lambdas concurrently.

Issue getting simple Fetch working in Netlify Functions

I've been working through the workshops for Netlify functions and have stumbled getting a simple Fetch response to work. When running the sample at:
https://github.com/DavidWells/netlify-functions-workshop/blob/master/lessons-code-complete/use-cases/5-fetching-data/functions/node-fetch/node-fetch.js
const fetch = require('node-fetch')
const API_ENDPOINT = 'https://cat-fact.herokuapp.com/facts'
exports.handler = async (event, context) => { let response
try {
response = await fetch(API_ENDPOINT)
// handle response
} catch (err) {
return {
statusCode: err.statusCode || 500,
body: JSON.stringify({
error: err.message
})
} }
return {
statusCode: 200,
body: JSON.stringify({
data: response
}) } }
but I just get the following response:
{"data":{"size":0,"timeout":0}}
The build process is working fine, and I've tried other end points but all give the same results.
Here is a working version of your original code using node-fetch and the callback.
// import fetch from 'node-fetch';
const fetch = require('node-fetch')
const checkStatus = (res) => {
if (res.ok) { // res.status >= 200 && res.status < 300
return res.json()
} else {
throw new Error(res.statusText);
}
}
exports.handler = async function(event, context, callback) {
try {
const response = await fetch('https://cat-fact.herokuapp.com/facts')
const data = await checkStatus(response)
callback(null, {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
} catch (error) {
callback(error)
}
}
Note: Netlify functions are just AWS functions and you can read about the AWS Lambda Function Handler in the AWS docs
For async functions, you return a response, error, or promise to the runtime instead of using callback.
I like to use the method recommended in the docs, since it is supported and save the callback for non-async functions.
Here is a working version returning a promise to the async function instead.
// import fetch from 'node-fetch';
const fetch = require('node-fetch')
exports.handler = async (event, context) => {
return new Promise((resolve, reject) => {
fetch('https://cat-fact.herokuapp.com/facts')
.then(res => {
if (res.ok) { // res.status >= 200 && res.status < 300
return res.json();
} else {
resolve({ statusCode: res.status || 500, body: res.statusText })
};
})
.then(data =>{
const response = {
statusCode: 200,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(data)
}
resolve(response);
})
.catch(err => {
console.log(err)
resolve({ statusCode: err.statusCode || 500, body: err.message })
})
})
}
This is just an example and there are other ways to handle the errors, but this example resolved the response rather than rejecting. It is not tested as a production example, but gives you an idea of the difference.
Here are some examples of Netlify functions.
On the handle response line you’ll want to await the json result:
response = await response.json();
Check the MDN article for Using Fetch for more details.
In the end I switched to the Axios library and it worked first time:
const axios = require('axios')
const API_ENDPOINT = 'https://jsonplaceholder.typicode.com/todos/1'
exports.handler = async (event, context) => {
let response
try {
response = await axios.get(API_ENDPOINT)
} catch (err) {
return {
statusCode: err.statusCode || 500,
body: JSON.stringify({
error: err.message
})
}
}
return {
statusCode: 200,
body: JSON.stringify({
data: response.data
})
}
}

Need to scan db and either display a message or update item

I'm using Dynamo DB in AWS. I've reviewed some of the documentation and now have a semi functional lambda function. I'm not too familiar with nodejs but figured "it's javascript! just treat it like that!".
So this function is about scanning the database in the user_handle db and if a user already has an existing handle, then alert the user that the handle already exists. If the handle does not exist, then update the user_handle item.
So my function updates the handle if it doesn't exist, but if it does, then I need to see this in my response.
Here's my lambda function:
const AWS = require('aws-sdk'); const docClient = new AWS.DynamoDB.DocumentClient({region: 'us-west-1'});
exports.handler = (event, context, callback) => {
let e = JSON.parse(event.body)
var params = {
TableName: event.stageVariables.user,
Key: { 'userId' : e.userId },
UpdateExpression: 'set user_handle = :user_handle',
ExpressionAttributeValues: {
':user_handle' : e.user_handle,
}
};
var scanParams = {
TableName : event.stageVariables.user,
FilterExpression : 'user_handle = :user_handle',
ExpressionAttributeValues : {':user_handle' : e.user_handle}
};
docClient.scan(scanParams, function(err, data) {
if (err) {
console.log("ERROR:", err);
let response = {
"statusCode": err.statusCode,
"headers": {},
"body": JSON.stringify(err)
}
console.log("RESPONSE", response)
callback(response)
} else {
let response = {
"statusCode": 200,
"body": JSON.stringify({"Success": true})
}
callback(null, response)
// console.log("RESPONSE", response)
// console.log("DATA", data)
if( data.Count >= 1 ){
let handleExistsResponse = {
"statusCode": 200,
"body": JSON.stringify({"Success": false})
}
console.log("HANDLE IT", handleExistsResponse)
callback(null, handleExistsResponse)
} else {
docClient.update(params, function(err, data) {
if (err) {
console.log("ERROR:", err);
let response = {
"statusCode": err.statusCode,
"headers": {},
"body": JSON.stringify(err)
}
console.log(response)
callback(response)
} else {
let response = {
"statusCode": 200,
"body": JSON.stringify({"Success": true})
}
callback(null, response)
}
});
}
}
// console.log("DATA", data);
});
};
Here's the DB function in my React Native App:
export async function createUserHandle() {
return new Promise((resolve, reject) => {
let { auth } = store.getState()
let reqBody = {
userId: auth.user.username,
user_handle: auth.user_handle,
}
let path = '/u/create-user-handle'
let myInit = {
headers: { 'Content-Type': 'application/json' },
body: reqBody,
// response: true,
}
API.post(apiName, path, myInit)
.then((resp) => {
console.log('response from user handle', resp)
resolve(resp)
})
.catch((error) => {
console.warn('Create USER Handle ERROR', error)
reject(error)
})
})
}
In the Lambda I was hoping that if (data.Count >= 1){} can give me a response of false coming from
let handleExistsResponse = {
"statusCode": 200,
"body": JSON.stringify({"Success": false})
}
and if it is false, then I can display the appropriate message on the front end. but the response i'm getting is true if i console log it on the front end.
( also, wouldn't mind a code review on this, probably my 3rd time writing a function like this. thanks!)
Figured it out..
} else {
let response = {
"statusCode": 200,
"body": JSON.stringify({"Success": true})
}
callback(null, response)
// console.log("RESPONSE", response)
// console.log("DATA", data)
if( data.Count >= 1 ){
The callback happening after the response kept the next call back after the condition to go through. I'm now getting the response I needed. Feels good :D
Still wouldn't mind a code review :)

Node Returning Try Catch Nested Async Errors

I have created a nodejs/express api, which is a wrapper around another api.
I am making async calls with try and catch. I am wondering in the inner async call if it catches an error will this error be caught by the outer async call.
The transaction.controller is my api which calls the transaction.repository to an external api. The function getTransactionResult is throwing an error in the catch part. However, this does NOT loop back to the catch part of my show function.
router.ts
this.router.get('/:id', this.controller.show.bind(this.controller));
transaction.controller.ts
public async show(req: Request, res: Response): Promise<any> {
const params = req.params;
const token = req.headers.token;
let transaction;
try {
transaction = await this.transactionRepo.getTransactionResult(params.id, token);
console.log("instead carries on here"); // 2
if (transaction.success === false) {
return res.status(404).json({
success: false,
status: 404,
data: transaction,
message: "Failed to retrieve transaction result"
});
}
return res.status(200).json({
success: true,
status: 200,
data: transaction,
message: "Successfully retrieved transaction result"
});
} catch (err) {
//console.log("Does not call this");
return res.status(500).json({
success: false,
status: 400,
data: err,
message: "The server threw an unexpected error",
});
}
transaction.repository.ts
public async getTransactionResult(transactionId: string, token: string) {
const url = this.config.api_url + "/api/transactions/" + transactionId + "/summary";
const options = {
uri: url,
headers: {
'token': token
},
body: {
},
json: true,
resolveWithFullResponse: true
}
let result;
try {
result = await request.get(options);
if (result.statusCode !== 200) {
return { "success": false, data: result }
}
return { "success": true, data: result }
} catch (err) {
console.log("CAUGHT ERROR"); // 1
return err;
}
}
You need to rethrow the error not return it.
By doing return err you are resolving the Promise which mean the async operation succeeded. Hence why your out try-catch in transaction.controller.ts does not catch anything.
Either:
Don't catch the error and let it bubble up
Rethrow the error

Resources