0
votes

Typescript newbie here. I am working on an AWS Lambda function by using typescript with classes. I am exporting an async handler at the end. When I invoke my function from AWS SAM CLI then I am getting error of;

{"errorType":"TypeError","errorMessage":"Cannot read property 'test' of undefined","stack":["TypeError: Cannot read property 'test' of undefined"," at Runtime.handler (/var/task/src/lambda/create-cost-lambda.js:12:56)"," at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"]}

create-cost-lambda.ts

class CreateCostLambda {
    private readonly foobarRepository: FoobarRepository;

    constructor() {
        this.foobarRepository = new FoobarRepository();
    }

    async handler(event: APIGatewayProxyEventV2) : Promise<APIGatewayProxyResultV2> {
        const result = await this.foobarRepository.test();
        console.log(result);

        return {
            body: JSON.stringify(result),
            statusCode: 200,
        };
    }
}

export const { handler } = new CreateCostLambda();

Here is a very basic class represents a repository.

foobar-repository.ts

export class FoobarRepository {
    private readonly awesomeValue: string;

    constructor() {
        this.awesomeValue = 'John Doe';
    }

    async test(): Promise<string> {
        return this.awesomeValue;
    }
}

I am almost sure it is because of the way I am exporting the handler and how aws-sam internally runs the handler. But I might be wrong and it can be typescript thing that I am missing. Please let me know if you need more information and thanks a lot for the help!

1
async handler(event: APIGatewayProxyEventV2) : Promise<APIGatewayProxyResultV2> needs to be an arrow function. You are losing context for you function. Write it like handler = async (event: APIGatewayProxyEventV2) : Promise<APIGatewayProxyResultV2> => {..} - Townsheriff
@Townsheriff thanks for the approach. I was considering more of an Object Oriented way of implementation which is discussed in article blog.10pines.com/2019/07/23/…. So, exporting handler with bind worked as expected. Something like; export const createCostLambda = new CreateCostLambda(); export const handler = createCostLambda.handler.bind(createCostLambda); Is it a bad approach for what I am asking? Thanks! - quartaela
I'm not sure why you would want something like that. You need to bind otherwise you wont have access to context. I would go with just a function - module.exports.handler = (...) => {...}. Arrow functions bind the function to the context it self so you can write in your class arrow "methods" and it will work typescriptlang.org/play?#code/… - Townsheriff
Thanks you right. I went with the approach of just using an arrow function. - quartaela

1 Answers

1
votes

The short version is if you pass a function from an class, it loses it's reference to this.

I would solve this as follows:

const createCostLambda = new CreateCostLambda();
export const handler = createCostLambda.handler.bind(createCostLambda);

You can also ask yourself, does this need to be a class? The answer is: probably not. There's nothing gained from this in your sample.

const foobarRepository = new FoobarRepository();
export async function handler(event: APIGatewayProxyEventV2) : Promise<APIGatewayProxyResultV2> {
   const result = await foobarRepository.test();
   console.log(result);

   return {
     body: JSON.stringify(result),
     statusCode: 200,
   };
}

Fewer lines, no unneeded state. Javascript is not Java =)