AWS Lambda函数流程

时间:2018-12-03 15:59:13

标签: node.js amazon-web-services amazon-s3 aws-lambda

我的函数在lambda中的流动方式存在一些问题。我试图获取存储在S3中的值,将其增加,然后放回去。但是,我的程序并没有达到我的预期。我正在使用异步瀑布来运行我的功能流程。

这是我的代码:

let AWS = require('aws-sdk');
let async = require('async');

let bucket = "MY_BUCKET";
let key = "MY_FILE.txt";

exports.handler = async (event) => {
    let s3 = new AWS.S3();
    async.waterfall([
        download,
        increment,
        upload
        ], function (err) {
            if (err) {
                console.error(err);
            } else {
                console.log("Increment successful");
            }

            console.log("test4");
            return null;
        }
    );
    console.log("test5");

    function download(next) {
        console.log("test");
        s3.getObject({
            Bucket: bucket,
            Key: key
        },
        next);
    }
    function increment(response, next) {
        console.log("test2");
        console.log(response.Body);
        let newID = parseInt(response.Body, 10) + 1;
        next(response.ContentType, newID);
    }
    function upload(contentType, data, next) {
        console.log("test3");
        s3.putObject({
            Bucket: bucket,
            Key: key,
            Body: data,
            ContentType: contentType
        },
        next);
    }
};

我的日志上只得到test和test5。我的印象是,在下载功能之后,如果可以的话,增量应该运行,或者在出现错误的情况下,瀑布末尾的回调函数应该运行。该程序不会在执行时产生错误,并且似乎不会执行任何一个功能。

有人可以指导我了解我所缺少的吗?

编辑:所以看来我的问题与我的函数声明有关。默认模板将其声明为async(event)。我认为这很奇怪,因为通常将它们声明为(事件,上下文,回调)。切换到更高版本(甚至没有异步的(事件))可以解决此问题。看来我的问题是将函数作为异步调用。这阻止了瀑布异步调用??有人可以详细说明吗?

2 个答案:

答案 0 :(得分:2)

您的问题是您的处理程序被声明为async函数,这将自动为您创建一个Promise,但是由于您根本没有等待,因此您的函数实质上是同步结束的。

有两种方法可以解决此问题,我们将介绍所有这些方法。

  1. 请勿使用承诺,请使用回调函数,因为async库旨在使用。
  2. 请勿使用async库或回调,而应使用async / await
  3. 将两者混合在一起,做出自己的承诺,并手动resolve / reject

1。不要使用诺言

在此解决方案中,您将删除async关键字,并添加lambda传递给您的回调参数。只需调用它将结束lambda,向其传递错误将表明函数已失败。

// Include the callback parameter ────┐
exports.handler = (event, context, callback) => {
    const params =[
      download,
      increment,
      upload
    ]
    async.waterfall(params, (err) => {
      // To end the lambda call the callback here ──────┐
      if (err) return callback(err);  //   error case ──┤
      callback({ ok: true });         // success case ──┘
    });
};

2。使用async / await

这里的想法是不使用回调样式,而是使用基于Promise的async / await关键字。如果返回承诺,lambda将使用该承诺来处理lambda完成而不是回调。

如果您有一个带有async关键字的函数,它将自动返回对您的代码透明的promise。

为此,我们需要修改您的代码以不再使用async库,并使其他函数也异步。

const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const Bucket = "MY_BUCKET";
const Key = "MY_FILE.txt";

async function download() {
  const params = {
    Bucket,
    Key
  }
  return s3.getObject(params).promise(); // You can await or return a promise
}

function increment(response) {
  // This function is synchronous, no need for promises or callbacks
  const { ContentType: contentType, Body } = response;
  const newId = parseInt(Body, 10) + 1;
  return { contentType, newId };
}

async function upload({ contentType: ContentType, newId: Body }) {
  const params = {
    Bucket,
    Key,
    Body,
    ContentType
  };
  return s3.putObject(params).promise();
}

exports.handler = async (event) => {
    const obj = await download(); // await the promise completion
    const data = increment(obj);  // call synchronously without await
    await upload(data)

    // The handlers promise will be resolved after the above are
    // all completed, the return result will be the lambdas return value.
    return { ok: true };
};

3。混合承诺和回调

在这种方法中,我们仍在使用基于回调的异步库,但是我们的外部函数是基于承诺的。很好,但是在这种情况下,我们需要手动做出自己的承诺,并在瀑布处理程序中解决或拒绝它。

exports.handler = async (event) => {
  // In an async function you can either use one or more `await`'s or
  // return a promise, or both.
  return new Promise((resolve, reject) => {
    const steps = [
      download,
      increment,
      upload
    ];
    async.waterfall(steps, function (err) {
      // Instead of a callback we are calling resolve or reject
      // given to us by the promise we are running in.
      if (err) return reject(err);
      resolve({ ok: true });
    });
  });
};

其他

除了您遇到的回调与承诺的主要问题之外,还有一些我注意到的小问题:

其他1

大多数情况下,您应该使用const而不是let。唯一应该使用let的情况是,如果您打算重新分配变量,则大多数时候不应该这样做。我想用一种不需要编写let的代码的方式来挑战您,它将帮助您总体上改善代码。

其他2

在瀑布步骤之一中,您将response.ContentType返回为next的第一个参数时遇到问题,这是一个错误,因为它将把它解释为错误。回调的签名为next(err, result),因此您应该在增量和上传功能中执行此操作:

function increment(response, next) {
  const { ContentType: contentType, Body: body } = response;
  const newId = parseInt(body, 10) + 1;
  next(null, { contentType, newId }); // pass null for err
}
function upload(result, next) {
  const { contentType, newId } = result;
  s3.putObject({
      Bucket: bucket,
      Key: key,
      Body: newId,
      ContentType: contentType
  },
  next);
}

如果您在调用下一个null时未通过undefinedasync来输入错误,则会将其解释为错误,并且会跳过瀑布的其余部分并直接完成处理程序传递该错误。

其他3

您需要了解的关于context.callbackWaitsForEmptyEventLoop的信息是,即使您正确完成了该功能,以上述方法之一,您的lambda仍可能会挂起并最终超时而不是成功完成。根据此处的代码示例,您可能不必担心这一点,但是发生这种情况的原因是,如果您碰巧没有正确关闭某些内容,例如与数据库或websocket的持久连接或类似的内容, 。在执行lambda的开始时将此标志设置为false会导致该进程退出,而不管任何使事件循环保持活动的状态,并且将迫使它们不合常规地关闭。

在以下情况下,您的lambda可以成功完成工作,甚至返回成功结果,但它将一直挂起直到超时,并报告为错误。甚至可以反复触发它,具体取决于触发方式。

exports.handler = async (event) => {
  const db = await connect()
  await db.write(data)
  // await db.close() // Whoops forgot to close my connection!
  return { ok: true }
}

在那种情况下,简单地调用db.close()就可以解决问题,但是有时它并不清楚事件循环中正在徘徊的是什么,您只需要大锤类型的解决方案来关闭lambda,这就是{{1 }}是为了!

context.callbackWaitsForEmptyEventLoop = false

函数返回后,上面的代码将立即完成lambda,杀死所有连接或仍然存在于事件循环中的其他任何东西。

答案 1 :(得分:0)

您的函数在解析瀑布之前终止。也就是说,根本不执行异步调用。这就是为什么您看不到console.log函数中有任何waterfall调用,而仅看到在调用async.waterfall之后立即被同步调用的原因。

不确定AWS Lambda对async.waterfall的支持程度如何,但是由于原生支持promise并执行相同的功能(位置更少),因此您可以使用promises。您的代码如下所示:

module.exports.handler = (event,context) =>
    s3.getObject({
        Bucket: bucket,
        Key: key
    }).promise()
        .then(response => ({
            Body: parseInt(response.Body, 10) + 1,
            ContentType: response.contentType,
        }))
        .then(modifiedResponse => s3.putObject({
            Bucket: bucket,
            Key: key,
            Body: modifiedResponse.data,
            ContentType: modifiedResponse.contentType}).promise())
        .catch(err => console.error(err));