未通过地图循环添加DynamoDB记录

时间:2019-12-20 05:09:45

标签: node.js aws-lambda amazon-dynamodb serverless

我正在使用Axios从API获取价格(通常为数千美元),然后将其存储在DynamoDB中。如果我在本地调用lambda函数,那么一切都会按预期工作,但是如果我部署该函数并使用AWS CLI调用它,它将不再在DynamoDB中存储任何值。我在请求中收到的数据以及Axios调用的响应都相同。

我不知何故认为这是异步函数调用DynamoDB的范围问题,但我无法解决。期待您的建议。让我知道您是否需要更多代码。

updatePrice.js

import { updatePrice } from "./libs/pricing-lib";
import {
    success,
    failure
} from "./libs/response-lib";

export async function main(event, context) {
    try {
        let result = await updatePrice(event.pathParameters.id, event.pathParameters.date);
        return success(result);
    } catch(e) {
        return failure(e);
    }
}

dynamodb-lib-js

import AWS from "aws-sdk";
export function call(action, params) {
    const dynamoDb = new AWS.DynamoDB.DocumentClient();
    return dynamoDb[action](params).promise();
}

pricing-lib.js

export async function updatePrice(stockid, from) {
  try {
    let url = getUrl(stockid, from);
    const resultEodApi = (await axios.get(url)).data;

    resultEodApi.map((price) => {
      try {
        let priceParams = {
          TableName: process.env.pricesTableName,
          Item: {
            stockid: stockid,
            date: price.date,
            close: price.close
          }
        };
        dynamoDbLib.call("put", priceParams);
      } catch (e) {
        return e;
      }
    });

    return true;
  } catch (e) {
    return e;
  }
}

2 个答案:

答案 0 :(得分:4)

稍微扩大Ashish的答案。

问题:

如Ashish所说,put操作在Lambda部署中不起作用的原因是该调用是异步执行的。

dynamoDb[action](params).promise()的调用开始异步put操作,并返回Promise对象。当put操作返回时,承诺将被解决。

但是,在您的代码中,您既不会await来解决承诺,也不会将承诺作为处理程序的输出返回。对updatePrice的调用终止并返回undefined,此时 AWS Lambda暂停执行该函数。因此,put呼叫永远不会进行。

为什么本地执行与远程执行之间有区别?

看到远程执行与本地执行不同的原因是本地的node.js进程和Lambda函数具有不同的语义。

在本地运行node.js进程时,仅在所有承诺都得到解决后,node.js进程才会终止 1

Lambda执行的行为有所不同。 Lambda在等待执行 2 之前不等待承诺被解决。相反, Lambda一旦解决了处理程序返回的承诺,就会终止执行。在您的情况下,处理程序函数返回true 3 ,因此将立即解决该问题,并终止执行。那时,您的put呼叫尚未解决。

为说明不同之处,请考虑以下代码:

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}       

module.exports.hello = async (event) => {
  sleep(300).then(() => console.log("Finished doing my asynchronous thing, can exit now!"))    
  console.log("Function execution done!")
  return true;
};

// call the handler when running locally:
if (require.main === module) { module.exports.hello(); }

在本地运行此函数时,函数执行完成,但是节点进程仍在运行,直到解决了Promise。在本地运行,您将获得以下输出(请注意顺序):

> Function execution done!
> Finished doing my asynchronous thing, can exit now!

在lambda上运行,当函数执行结束时,其余的运行也将运行。承诺永远不会实现。我们将获得以下输出:

START RequestId: <id> Version: $LATEST
2019-12-26T09:04:28.843Z    <id>    INFO    Function execution done!
END RequestId: <id>
REPORT RequestId: <id>  Duration: 3.37 ms   Billed Duration: 100 ms Memory Size: 1024 MB    Max Memory Used: 71 MB  Init Duration: 114.44 ms    

我们只得到Function execution done!印刷品。还要注意,执行持续时间仅为3.37ms。在AWS停止进程之前,我们异步运行的300毫秒睡眠没有时间解决。

解决问题

来自AWS Lambda developer guide

  

如果您的代码执行异步任务,请返回一个承诺以确保其完成运行。当您解决或拒绝承诺时,Lambda会将响应或错误发送给调用方。

您可以使用Ashish的解决方案-返回您创建的承诺。或者,您可以显式await Promise.all。无论哪种情况,重要的是要确保在从函数返回之前,不要丢失创建的任何承诺。


1 更具体地说,它等待事件循环为空。

2 实际上它暂停执行。下次调用处理程序时,将从同一位置继续执行。如果您对处理程序进行了多次连续调用,则某些put可能会通过,具体取决于时间安排以及aws-sdk库如何处理执行流中的此类中断,但这是很难预测。

3 实际上,async函数的返回值始终是包装返回值的承诺。因此,在您的情况下,您拥有的是return true;true被包装在Promise对象中,该对象将立即解析并终止执行。

答案 1 :(得分:0)

问题在于您在map内进行异步操作时定价lib.js。您将需要从地图内部返回promise,并将地图包装在promise all中。看到这里

export async function updatePrice(stockid, from) {
  try {
    let url = getUrl(stockid, from);
    const resultEodApi = (await axios.get(url)).data;

    return Promise.all(resultEodApi.map((price) => {
      try {
        let priceParams = {
          TableName: process.env.pricesTableName,
          Item: {
            stockid: stockid,
            date: price.date,
            close: price.close
          }
        };
        return dynamoDbLib.call("put", priceParams);
      } catch (e) {
        return e;
      }
    }));

    return true;
  } catch (e) {
    return e;
  }
}