I am working on a serverless function to be hosted on Netlify to subscribe users to a mailchimp email list.
I am getting the following obscure error:
lambda response was undefined. check your function code again
Here is my function:
const handler = async function (event, context) {
try {
let body = JSON.parse(event.body);
mailchimp.setConfig({
apiKey: 'XXXXXXXXX',
server: 'us20',
});
const submit = async () => {
const response = await mailchimp.lists.addListMember("XXXXXXXX", {
email_address: body.email.toLowerCase(),
status: 'subscribed'
});
if (response.errors !== undefined && response.errors.length) {
throw new Error(response.errors);
}
}
submit().then(response => {
console.log(response);
return {
statusCode: 200,
body: JSON.stringify({ response }),
}
}).catch(errors => {
return {
statusCode: 500,
body: JSON.stringify({ errors }),
}
});
} catch (error) {
// output to netlify function log
console.log(error);
return {
statusCode: 500,
// Could be a custom message or object i.e. JSON.stringify(err)
body: JSON.stringify({ msg: error.message }),
}
}
}
module.exports = { handler }
I think the issue may be because nothing is being returned after calling submit(), but I am not sure how best to return it. I still can't quite get my head around promises.
I am really hoping someone can point me in the right direction.
Many thanks
David
You are on the right track, nothing is returned in the submit function so the response will always be undefined.
Also You are using .then with async/await syntax which is ok, async/await is just a cleaner way of using promises so you typically use one or the other. And nesting catch blocks here is unnecessary. I would rewrite it to something like this:
const handler = async function (event, context) {
try {
let body = JSON.parse(event.body);
mailchimp.setConfig({
apiKey: 'XXXXXXXXX',
server: 'us20',
});
const submit = async () => {
const response = await mailchimp.lists.addListMember("XXXXXXXX", {
email_address: body.email.toLowerCase(),
status: 'subscribed'
});
if (response.errors !== undefined && response.errors.length) {
throw new Error(response.errors);
}
return response;
}
const response = await submit();
return {
statusCode: 200,
body: JSON.stringify({ response }),
}
} catch (error) {
// output to netlify function log
console.log(error);
return {
statusCode: 500,
// Could be a custom message or object i.e. JSON.stringify(err)
body: JSON.stringify({ msg: error.message }),
}
}
}
If you are still struggling with promises, Id recommend reading the docs for them as well as the docs for async/await.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Related
I just changed the server from const server = "https://backend.yourguide.pk" to const server = "https://stage.yourguide.pk", and now this error is shown. How to resolve it?
getServerSideProps's code:
export async function getServerSideProps(context) {
let experience = [];
const server = 'stage.yourguide.pk';
try {
const data = await fetch(
`${server}/api/user/getallexperience?title=${context.params.experiencesTitle}`,
{
method: 'GET',
headers: { 'Content-Type': 'application/json' },
}
);
experience = await data.json();
} catch (error) {
console.log('error is ', error);
}
return {
props: {
title: context.params.experiencesTitle,
experience: experience.results,
},
};
}
error - SerializableError: Error serializing .experiences returned from getServerSideProps in "/".
Reason: undefined cannot be serialized as JSON. Please use null or omit this value.
For some reason, experience is being set to undefined, and undefined is not a valid JSON value. This can be addressed with the help of a check, like so:
export async function getServerSideProps(context) {
let experience;
const server = "stage.yourguide.pk";
try {
const data = await fetch(
`${server}/api/user/getallexperience?title=${context.params?.experiencesTitle}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
}
);
experience = await data.json();
} catch (error) {
console.log("error is ", error);
}
return {
props: {
title: context.params?.experiencesTitle || null,
experience: experience?.results || null,
},
};
}
Now, why you are getting undefined? Well, this can be because you are not using the correct endpoint, or path, or not giving the correct params, etc. Also, your error is saying .experiences while you have .experience.
Make sure you are not making any typos.
I have an AWS Lambda function called getTables that runs when you hit an API Gateway endpoint.
I would like to mock an exception being thrown by a section of my code that uses de AWS Glue SDK.
How can I properly mock an exception being thrown by a method and how should I assert it?
The test that I have doesn't seem to be working cause I get an unexpected exception which indicates that another part of the code is trying to use the response of the mocked code (meaning it didn't throw?):
it('should throw on db not found', async () => {
mockGetTables.mockReturnValue(new Error("EntityNotFoundException"));
await getTables(MOCK_REQUEST_EVENT_FAIL, mockContext, mockCallback);
expect(mockCallback).rejects.toEqual('EntityNotFoundException');
});
Here's my Lambda code:
export const getTables = async (
event: APIGatewayProxyEvent,
_context: Context,
callback: Callback<APIGatewayProxyResult>
) => {
console.log('Executing /getTables Activity.');
console.log(`/getTables event: ${JSON.stringify(event)}.`);
try {
const { queryStringParameters } = event;
let catalogIdParam: string = null;
let databaseNameParam: string = null;
let maxResultsParam: number = null;
let nextTokenParam: string = null;
if (queryStringParameters) {
const { catalogId, databaseName, maxResults, nextToken } = queryStringParameters;
catalogIdParam = catalogId || null;
databaseNameParam = databaseName || null;
maxResultsParam = maxResults ? parseInt(maxResults) : null;
nextTokenParam = nextToken || null;
}
const glueClientInstance: GlueClient = GlueClient.getInstance();
//I'd like to mock the following async method and throw "EntityNotFoundException"
//just like API Gateway (or lambda) would do.
const { TableList, NextToken }: GetTablesResponse = await glueClientInstance.getTables(
databaseNameParam,
catalogIdParam,
maxResultsParam,
nextTokenParam
);
const pandaUITableList: PandaUIGlueTable[] = convertToPandaUITableList(TableList);
callback(null, {
statusCode: 200,
body: JSON.stringify({
TableList: pandaUITableList,
NextToken,
}),
});
} catch (error) {
console.log(`An error ocurred while executing /getTables activity: ${JSON.stringify(error)}`);
if (error.code === 'EntityNotFoundException') {
callback(null, {
statusCode: 400,
body: JSON.stringify({
error: error.message,
}),
});
}
//Generic/CatchAll handler that I'll test later once I figure this one out
handlerApiError(error, callback);
}
};
For reference, this is the atual error I'm trying to mock and throw:
{
"message": "Database fdv_lin not found.",
"code": "EntityNotFoundException",
"time": "2022-03-29T00:47:07.475Z",
"requestId": "fff5d84c-59de-441d-a204-e08ede830931",
"statusCode": 400,
"retryable": false,
"retryDelay": 76.52610613917457
}
You can use jest's toHaveBeenCalledWith() or toHaveBeenLastCalledWith() to assert that the callback is called with the appropriate payload in the catch clause. In this case, it is the below example.
callback(null, {
statusCode: 400,
body: JSON.stringify({
error: error.message,
}),
});
Possible Solution
const mockCallback:Callback<APIGatewayProxyResult> = jest.fn();
it('should throw on db not found', async () => {
mockGetTables.mockReturnValue(new Error('EntityNotFoundException'));
await getTables(MOCK_REQUEST_EVENT_FAIL, mockContext, mockCallback);
expect(mockCallback).toHaveBeenCalledWith(null, {
statusCode: 400,
body: JSON.stringify({
error: {
message: 'Database fdv_lin not found.',
code: 'EntityNotFoundException',
time: '2022-03-29T00:47:07.475Z',
requestId: 'fff5d84c-59de-441d-a204-e08ede830931',
statusCode: 400,
retryable: false,
retryDelay: 76.52610613917457,
},
}),
});
So basically I'm working on a nextjs app which uses authentication. I have a 2 functions which I run on every page load. The first checks if jwt cookies exist and calls another function to validate the tokens if they don't exist. This function is ran from wrapper.getServerSideProps and is passed in the context as ctx. This function works as intended.
export const checkServerSideCookie = (ctx) => {
const access = getCookie("access", ctx.req);
const refresh = getCookie("refresh", ctx.req);
if (access && refresh) {
return checkAuthentication(access, refresh);
} else return { isAuthenticated: false, token: null };
};
The second function is the token validator and this is where the issue arises. I have an object which I intended to update if the validation is successful and leave alone if it isn't. Here is the function
export const checkAuthentication = (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null,
};
const body = JSON.stringify({ token: access });
axios
.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
obj.isAuthenticated = true;
obj.token = access;
})
.catch((err) => {
// call new token function using refresh
console.log("it doesnt work");
});
return obj;
};
The issue is is that the .then does update the object, and when I console.log(obj) in the .then it shows the proper obj to return, however when I return the obj it still holds the initial values of false and null. I don't understand what the issue is. I try doing the return in the .then itself but it throughs this error
TypeError: Cannot destructure property 'isAuthenticated' of 'Object(...)(...)' as it is undefined.
What is the issue here? It all seems good but the updated obj isn't returned.
axios.post is async, you're returning the obj before it gets filled with data from the api response, you can use async/await to solve that :
export const checkAuthentication = async (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null
};
const body = JSON.stringify({ token: access });
try {
const res = await axios.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json"
}
});
obj.isAuthenticated = true;
obj.token = access;
} catch (e) {
// do something with the error
// call new token function using refresh
console.log("it doesnt work");
}
return obj;
};
usage (checkAuthentication now return a promise ) :
checkAuthentication(a, b).then((obj) => {
console.log(obj);
});
When you call checkAuthentication it immediately returns the obj with the default properties. You have an asynchronous operation specified in your function, however you don't wait until it's done. You'd have to rebuild your function the following way:
export const checkAuthentication = (access, refresh) => {
const obj = {
isAuthenticated: false,
token: null,
};
const body = JSON.stringify({ token: access });
return new Promise((resolve, reject) => {
axios
.post("http://localhost:8000/api/jwtoken/verify/", body, {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
resolve({
isAuthenticated: true,
token: access
})
})
.catch((err) => {
// call new token function using refresh
console.log("it doesnt work");
reject();
});
});
};
and then call your function the following way:
checkAuthentication(access, refresh)
.then(console.log)
.catch(console.log)
You, of course, have multiple options to make your function cleaner, such as by using async/await etc, but this should give you a quick overview of what is wrong.
Anyone has any idea about this and how would I be able to get the correct outcome?
I have used the Promise and async/await properties in here
layers = async () => {
return new Promise((resolve, reject) => {
let options = {
url: `http://geoserverIP/geoserver/rest/workspaces/datastores/featuretypes.json`,
auth: {
'user': 'admin',
'pass': 'geoserver'
}
}
request(options, (err, resp, body) => {
if (!err && resp.statusCode == 200) {
return resolve(JSON.parse(body))
} else if (!err) {
return reject({
status: 404,
errors: [{
messages: ["Workspace or datastore not found"]
}]
})
} else {
return reject({
status: 500,
errors: [{
messages: ["Failed connection with geoserver"]
}]
})
}
})
})
}
console.log(layers())
I expect to get the list of layers but I get "Promise {pending}"
When you mark a function as async by default returns a Promise also you are returning Promise explicitly.
To use the value either you can await the function or can make a .then chain.
(async ()=>{
const data = await layers();
console.log(data);
})();
or
layers().then((data)=>{
console.log(data)
});
Note: As you are not using any await keyword inside the function you don't need to mark it as async
EDIT1: You can use axios instead of request, it by default returns promise.
I'm trying to return the login status from the Cognito callback function, which is written in the NodeJS Lambda. However when I call the API the response keep loading and I'm getting warning error.
Here is my code:
'use strict';
global.fetch = require('node-fetch');
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
module.exports.hello = async (event, context) => {
return {
statusCode: 200,
body: JSON.stringify({
message: "Hello there"
}),
};
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
module.exports.register = async (event, context, callback) => {
let poolData = {
UserPoolId : 'xxxxx', // Your user pool id here
ClientId : 'xxxxxxx' // Your client id here
} // the user Pool Data
let userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
let attributeList = [];
let dataEmail = {
Name : 'email',
Value : 'test#gmail.com'
};
let dataName = {
Name : 'name',
Value : 'Jack'
};
var dataPhoneNumber = {
Name : 'phone_number',
Value : '+94234324324234' // your phone number here with +country code and no delimiters in front
};
let attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
let attributeName = new AmazonCognitoIdentity.CognitoUserAttribute(dataName);
var attributePhoneNumber = new AmazonCognitoIdentity.CognitoUserAttribute(dataPhoneNumber);
attributeList.push(attributeEmail);
attributeList.push(attributeName);
attributeList.push(attributePhoneNumber);
userPool.signUp('test#gmail.com', 'H1%23$4jsk', attributeList, null, function(err, result){
let data = {};
if (err) {
callback(null, {
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
}),
});
} else {
let cognitoUser = result.user;
callback(null, {
statusCode: 200,
body: JSON.stringify({
status: 'SUCCESS',
message: '',
data: {
username: cognitoUser.getUsername(),
id: result.userSub
}
}),
});
}
})
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
The warning error as follows:
Serverless: Warning: handler 'register' returned a promise and also uses a callback!
This is problematic and might cause issues in your lambda.
Serverless: Warning: context.done called twice within handler 'register'!
serverless.yml
service: test-auth
plugins:
- serverless-offline
provider:
name: aws
runtime: nodejs8.10
stage: dev
region: us-east-1
functions:
hello:
handler: handler.hello
events:
- http:
path: message
method: get
register:
handler: handler.register
events:
- http:
path: register
method: post
Any help would be appreciated, Thanks in advance.
EDIT (2019-04-01):
module.exports.register = (event, context) => {
...
userPool.signUp('test#gmail.com', 'H1%23$4jsk', attributeList, null, function(err, result){
// for testing purpose directly returning
return {
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
})
}
})
};
Its exactly what the error message states.
All async functions return promises.
module.exports.register = async (event, context, callback) => {}
You are also using the callback by calling
callback(null, {
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
}),
});
Instead of using the callback, just return the either an error or a valid response.
Well the error is accurate. async wraps your return with promise. Either use callback all the way through like:
global.fetch = require('node-fetch');
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
// remove async
module.exports.register = (event, context, callback) => {
...
// if you're using callback, don't use return (setup your callback to be able to handle this value as required) instead do:
// calback({ message: 'Go Serverless v1.0! Your function executed successfully!', event })
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
Or don't use callback, use async/await (Promise) all the way through like:
module.exports.register = async (event, context) => {
...
// needs promise wrapper, when using with promise, you might want to break up your code to be more modular
const mySignUp = (email, password, attributes, someparam) => {
return new Promise((resolve, reject) => {
userPool.signUp(email, password, attributes, someparam, function(err, result) {
let data = {};
if (err) {
reject({
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
}),
});
} else {
let cognitoUser = result.user;
resolve({
statusCode: 200,
body: JSON.stringify({
status: 'SUCCESS',
message: '',
data: {
username: cognitoUser.getUsername(),
id: result.userSub
}
}),
});
}
})
});
}
// call the wrapper and return
return await mySignUp('test#gmail.com', 'H1%23$4jsk', attributeList, null);
// don't use double return
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
Now register will return a promise. Elsewhere in your code you can call register like:
var result = register();
result
.then(data => console.log(data))
// catches the reject from Promise
.catch(err => console.error(err))
or in async/await function (Note: `await` is valid only inside `async` function)
async function someFunc() {
try {
var result = await register();
// do something with result
console.log(result);
} catch (err) {
// reject from Promise
console.error(err)
}
}
Also note use strict is not required here as node modules use strict by default.
You are using an async function with a call back.
Try it this way:
Remove the callback from the async function.
async (event, context)
And modify the return as:
if (err) {
return {
statusCode: 500,
body: JSON.stringify({
status: 'FAIL',
message: err.message
})
}
}
And put an await on the function call.
If it helps anyone else catching this, you can add headers to the return:
return {
statusCode: 200,
headers: {"Content-Type": "application/json"},
body: JSON.stringify(response.data)
};