0

I have been stuck with this for a couple of hours now, cannot understand why this simple async function is returning an empty array? I am trying to query a number of items from my DynamoDB table using a Lambda function but I cannot get the function to pause using async/await and I cannot understand why. I have logged following the push and the array is being filled up with lovely objects, but it just doesn't wait for it to finish before calling done. Please. Help. Going crazy.

'use strict';

const AWS = require('aws-sdk');

const docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = async(event, context, callback) => {

    const done = (err, res) => {

        const response = {
            statusCode: err ? '400' : '200',
            body: err ? JSON.stringify(err) : JSON.stringify(res),
            headers: {
                'Access-Control-Allow-Origin': '*'
            }
        };

        callback(null, response);

    };

    const { body } = event;

    // If in prod env, parse

    if (typeof body === 'string') {
        body = JSON.parse(body);
    }

    const groupIds = body.groupIds;

    let events = [];

    await groupIds.forEach(groupId => docClient.query({
        TableName: 'events',
        IndexName: 'groupId-creationDate-index',
        KeyConditionExpression: 'groupId = :g',
        ExpressionAttributeValues: { ':g': groupId }
    }, (err, data) => {
        if (err) {
            done(err);
        }
        else {
            events.push(...data.Items);
        }
    }));
    done(null, events);
};
John Rotenstein
  • 241,921
  • 22
  • 380
  • 470
ejanson
  • 469
  • 8
  • 16

3 Answers3

3

This is less a problem of AWS Lambda and a general Javascript question you need to async and await a little deeper into your AWS calls.

Effectively though you have two problems,

  • You need to async the actual AWS call (make it return a promise). You can see a good example on AWS SDK and async/await in this blog post.
  • Array.prototype.forEach is not an async method (insert note about node event bus yadda yadda) A decent writeup on this can be found here.

You could try something like this:

//Copied from https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array)
  }
}

asyncForEach(
    groupIds, 
    groupId => 
        await docClient.query({ ExpressionAttributeValues: { ':g': groupId }, ...params })
            .promise()
)

All that being said I recommend you use something like the node module async which will let you fetch things in parallel etc... and still use async and await.

Cheruvian
  • 5,628
  • 1
  • 24
  • 34
1

A for ... of Loop works.

for (let groupId of groupIds){
   //async code here
}

reference

  • Yes, exactly. I was having unexpected results with `Lambda` to `Lambda` invocation due to `forEach`. Using `for` solved that. – W.M. Oct 17 '22 at 18:45
0

Returns an empty function because it is just asynchronous, when await groupIds.forEach is resolved, it passes to the stack of the api of js, and the      done (null, events); is resolved before the promise is completed, to avoid this problem you could do the following, that your arrangement of events, resolve an array of promises and this when it is resolved, just there send it to the callback.

You could do something like this.

let arr=groupIds.map(groupId => docClient.query({
        TableName: 'events',
        IndexName: 'groupId-creationDate-index',
        KeyConditionExpression: 'groupId = :g',
        ExpressionAttributeValues: { ':g': groupId }
    }))
Promise.all(arr).then(events=>done(null,events))

I do not know the docClient API, but it is enough that it returns a promise (the callbacks are out of date) so that the variable arr has an array of Promises and these are resolved later.