【问题标题】:DynamoDB record not added through map loopDynamoDB 记录未通过映射循环添加
【发布时间】:2020-04-12 15:56:34
【问题描述】:

我正在使用 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();
}

定价-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;
  }
}

【问题讨论】:

  • TableName: process.env.pricesTableName 看起来很可疑。我会仔细检查你的 YAML 文件中的引用,看看会发生什么,除非这就是你所说的你的表。
  • 这只是一种奇怪的表格命名方式。我认为如果这是错误的,本地调用的函数也不会工作,对吧?

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


【解决方案1】:

稍微扩展 Ashish 的回答。

问题:

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

dynamoDb[action](params).promise() 的调用启动异步put 操作,并返回一个Promise 对象。当put 操作返回时,promise 将被解析。

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

为什么本地执行和远程执行有区别?

您看到远程执行和本地执行之间存在差异的原因是本地 node.js 进程和 Lambda 函数具有不同的语义。

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

Lambda 执行的行为不同。 Lambda 在终止执行之前不会等待 Promise 解决2。相反,Lambda 在解析处理程序返回的承诺后立即终止执行。在您的情况下,处理程序函数返回true3,因此它会立即解决,并且执行终止。在那个时间点,您的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.37 毫秒。在 AWS 停止进程之前,我们异步运行的 300 毫秒睡眠没有时间解决。

解决问题

来自AWS Lambda developer guide

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

您可以使用 Ashish 的解决方案——返回您创建的承诺。或者,您可以明确地await Promise.all。在任何一种情况下,确保在从函数返回之前不会丢失任何创建的 Promise 是很重要的。


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

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

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

【讨论】:

    【解决方案2】:

    问题在于定价-lib.js,因为您在地图中执行异步操作。您将需要从地图内部返回承诺并将地图包装在所有承诺中。看这里

    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;
      }
    }
    

    【讨论】:

    • 非常感谢您查看我的代码。现在我收到一个 502 Bad Gateway 错误……有什么要寻找的指针吗?
    • 顺便说一下,如果我在本地调用新的 lambda 函数(使用 Promise.all 等)它可以工作,但如果我使用 AWS CLI 则不行。
    【解决方案3】:

    对于这个问题,我觉得还是使用批处理请求比较好。

    https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/dynamodb-example-table-read-write-batch.html

    我也有同样的问题,我重写了我的 util 函数以使用批处理请求,现在更容易理解了。

    【讨论】:

      猜你喜欢
      • 2014-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-16
      • 2018-10-22
      • 2013-05-25
      • 1970-01-01
      相关资源
      最近更新 更多