I have a Serverless Lambda function that, in response to an S3 s3:ObjectCreated event, tries to check if a separate item exists in an S3 bucket using the following bit of code using the AWS JavaScript SDK:

exports.somethingSomeSomething = async (event) => {

  event.Records.forEach(async (record) => {

    let tst = await s3.headObject({
      Bucket: "mybucket",
      Key: "something.gz"
    }).promise()

    console.log(tst)
  })

};

I'm quite rusty with promises in JS, so I'm not sure why this bit of code doesn't work. For reference, it just dies without outputting anything.

However, the following does work:

exports.somethingSomething = async (event) => {


    let tst = await s3.headObject({
      Bucket: "mybucket",
      Key: "something.gz"
    }).promise()

    console.log(tst)
    console.log("RED")

};

How can I get the initial bit of code working, and what am I doing wrong?

1 Answers

1
Thales Minussi On Best Solutions

It's because your code is async, but the function passed to your forEach loop is also async, so you have an async function invoking another chunk of async code, therefore you lose control of the flow. Whatever is inside forEach will run (although anything after forEach will run before whatever is inside forEach), but it will execute asynchronously and you are unable to keep track of its execution.

But if the code, as I said, will run, why don't you see the results?

Well, that's because Lambda will terminate before that code has the chance to execute. If you run the same piece of code locally, you'll see it will run just fine, but since the original code runs on top of Lambda, you don't have control when it terminates.

You have two options here:

The easiest is to grab the first item in the Records array because s3 events send one and only one event per invocation. The reason it is an array is because the way AWS works (a common interface for all events). Anyways, your forEach is not using anything of the Record object, but still if you wanted to use any properties of it, simply reference the 0th position, like so:

exports.somethingSomeSomething = async (event) => {
    const record = event.Records[0]

    //do something with record

    const tst = await s3.headObject({
        Bucket: "mybucket",
        Key: "something.gz"
    }).promise()

    console.log(tst)
};

If you still want to use a for loop to iterate through the records (although, again, unnecessary for s3 events), use a for of loop instead:

exports.somethingSomeSomething = async (event) => {
    for (const record of event.Records) {
        // do something with record
        const tst = await s3.headObject({
            Bucket: "mybucket",
            Key: "something.gz"
        }).promise()
        console.log(tst)
    }
};

Since for of is just a regular loop, it will use the async from the function it's being executed on, so await is perfectly valid inside it.

More on async/await and for..of