如果所有调用都是异步的,那么如何在lambda中构建顺序AWS服务调用?

时间:2014-12-31 05:36:13

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

我来自java背景,所以对于Lambda来说,需要一些关于Javascript约定的新手。

我有一个lambda函数,用于按特定顺序执行多个AWS任务,具体取决于上一个任务的结果。

鉴于每个任务都异步报告其结果,我想知道是否正确的方法确保它们都以正确的顺序发生,并且一个操作的结果可用于调用下一个函数。

似乎我必须在前一个函数的回调函数中调用每个函数,但似乎会有某种深度嵌套,并且想知道这是否是正确的方法。

例如,这些函数需要一个DynamoDB getItem,然后调用SNS获取一个端点,接着是一个SNS调用来发送消息,然后是DynamoDB写入。

在lambda javascript中正确的方法是什么,考虑到所有的异步性?

8 个答案:

答案 0 :(得分:4)

我喜欢@jonathanbaraldi的回答,但我认为如果你使用Promise管理控制流会更好。 Q库有一些方便的功能,比如nbind,它有助于将节点样式的回调API转换为承诺,就像aws-sdk一样。

因此,在此示例中,我将发送一封电子邮件,然后一旦电子邮件回复,我将发送第二封电子邮件。这基本上就是所要求的,按顺序调用多个服务。我使用then promises方法以垂直可读的方式管理它。还使用catch来处理错误。我认为只是简单地嵌套回调函数就会好得多。

var Q = require('q');
var AWS = require('aws-sdk');    
AWS.config.credentials = { "accessKeyId": "AAAA","secretAccessKey": "BBBB"};
AWS.config.region = 'us-east-1';

// Use a promised version of sendEmail
var ses = new AWS.SES({apiVersion: '2010-12-01'});
var sendEmail = Q.nbind(ses.sendEmail, ses);

exports.handler = function(event, context) {

    console.log(event.nome);
    console.log(event.email);
    console.log(event.mensagem);

    var nome = event.nome;
    var email = event.email;
    var mensagem = event.mensagem;

    var to = ['email@company.com.br'];
    var from = 'site@company.com.br';

    // Send email
    mensagem = ""+nome+"||"+email+"||"+mensagem+"";

    console.log(mensagem);

    var params = { 
        Source: from, 
        Destination: { ToAddresses: to },
        Message: {
        Subject: {
            Data: 'Form contact our Site'
        },
        Body: {
            Text: {
                Data: mensagem,
            }
        }
    };

    // Here is the white-meat of the program right here.
    sendEmail(params)
        .then(sendAnotherEmail)
        .then(success)
        .catch(logErrors);

    function sendAnotherEmail(data) {
        console.log("FIRST EMAIL SENT="+data);

        // send a second one.
        return sendEmail(params);
    }

    function logErrors(err) {
        console.log("ERROR="+err, err.stack);
        context.done();
    }

    function success(data) {
        console.log("SECOND EMAIL SENT="+data);
        context.done();
    }
}

答案 1 :(得分:2)

我不知道Lambda,但您应该将节点async library作为对异步函数进行排序的方法。

async让我的生活变得更轻松,而且我的代码更加有序,没有您在问题中提到的深层嵌套问题。

典型的异步代码可能如下所示:

async.waterfall([
    function doTheFirstThing(callback) {
         db.somecollection.find({}).toArray(callback);
    },
    function useresult(dbFindResult, callback) {
         do some other stuff  (could be synch or async)
         etc etc etc
         callback(null);
],
function (err) {
    //this last function runs anytime any callback has an error, or if no error
    // then when the last function in the array above invokes callback.
    if (err) { sendForTheCodeDoctor(); }
});

在上面的链接中查看异步doco。串行,并行,瀑布等等有许多有用的功能。异步被积极维护并且看起来非常可靠。

祝你好运!

答案 2 :(得分:1)

想到一个非常具体的解决方案是级联Lambda调用。例如,您可以写:

  1. Lambda函数从DynamoDB中获取内容,然后调用...
  2. ...一个Lambda函数,调用SNS获取端点,然后调用...
  3. ...通过SNS发送消息的Lambda函数,然后调用...
  4. ...写入DynamoDB的Lambda函数
  5. 所有这些函数都将前一个函数的输出作为输入。这当然是非常细粒度的,您可能决定对某些呼叫进行分组。这样做至少可以避免JS代码中的回调地狱。

    (作为旁注,我不确定DynamoDB与Lambda的集成程度如何.AWS可能会为记录发出更改事件,然后可以通过Lambda处理。)

答案 3 :(得分:1)

简短回答:

  

使用Async / Await —并使用扩展名.promise()调用AWS服务(例如SNS),以告诉aws-sdk使用该服务功能的承诺版本而不是基于回叫的版本。

由于您要按特定顺序执行它们,因此可以使用Async / Await,前提是您要从中调用它们的父函数本身是异步的。

例如:

let snsResult = await sns.publish({
    Message: snsPayload,
    MessageStructure: 'json',
    TargetArn: endPointArn
}, async function (err, data) {
    if (err) {
        console.log("SNS Push Failed:");
        console.log(err.stack);
        return;
    }
    console.log('SNS push suceeded: ' + data);
    return data;
}).promise();
  

重要的是最后的.promise()。有关以异步/基于承诺的方式使用aws-sdk的完整文档,请参见:https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html

要运行另一个aws-sdk任务,您可以类似地向该函数添加await和.promise()扩展名(假设可用)。

对于遇到此线程并实际上只是希望将promise推入数组并等待该WHOLE数组完成(不考虑promise首先执行)的任何人,我都会得到以下结果:

let snsPromises = [] // declare array to hold promises
let snsResult = await sns.publish({
    Message: snsPayload,
    MessageStructure: 'json',
    TargetArn: endPointArn
}, async function (err, data) {
    if (err) {
        console.log("Search Push Failed:");
        console.log(err.stack);
        return;
    }
    console.log('Search push suceeded: ' + data);
    return data;
}).promise();

snsPromises.push(snsResult)
await Promise.all(snsPromises)

希望能像我一样帮助通过Google偶然发现这一问题的人!

答案 4 :(得分:0)

我发现这篇文章似乎在原生javascript中有答案。

Five patterns to help you tame asynchronis javascript.

答案 5 :(得分:0)

我想提供以下解决方案,它只是创建一个嵌套的函数结构。

// start with the last action
var next = function() { context.succeed(); };

// for every new function, pass it the old one
next = (function(param1, param2, next) {
    return function() { serviceCall(param1, param2, next); };
})("x", "y", next);

这样做的目的是复制你想要进行的函数调用的所有变量,然后将它们嵌入上一次调用中。您想要向后安排活动。这实际上与制作金字塔回调相同,但是当你事先不知道函数调用的结构或数量时,它就会起作用。您必须将函数包装在闭包中,以便复制正确的值。

通过这种方式,我能够对AWS服务调用进行排序,使得它们以1-2-3结束并以关闭上下文结束。据推测,您也可以将其构造为堆栈而不是伪递归。

答案 6 :(得分:0)

刚刚看到这个旧线程。请注意,JS的未来版本将改进这一点。看看ES2017 async/await语法,它将异步嵌套回调混乱简化为类似代码的干净同步。 现在有一些polyfills可以根据ES2016语法为您提供此功能。

作为最后一个FYI - AWS Lambda now supports .Net Core,提供开箱即用的干净异步语法。

答案 7 :(得分:-2)

默认情况下,Javascript是异步的。

所以,你必须做的一切,不是使用这些库,你可以,但有简单的方法来解决这个问题。在这段代码中,我发送了包含事件数据的电子邮件,但如果您愿意,您只需要在函数内添加更多函数。

重要的是context.done();将会是,他将结束你的Lambda功能。你需要把他放在最后一个函数的末尾。

var AWS = require('aws-sdk');    
AWS.config.credentials = { "accessKeyId": "AAAA","secretAccessKey": "BBBB"};
AWS.config.region = 'us-east-1';
var ses = new AWS.SES({apiVersion: '2010-12-01'});

exports.handler = function(event, context) {

    console.log(event.nome);
    console.log(event.email);
    console.log(event.mensagem);

    nome = event.nome;
    email = event.email;
    mensagem = event.mensagem;

    var to = ['email@company.com.br'];
    var from = 'site@company.com.br';

    // Send email
    mensagem = ""+nome+"||"+email+"||"+mensagem+"";

    console.log(mensagem);
    ses.sendEmail( { 
       Source: from, 
       Destination: { ToAddresses: to },
       Message: {
           Subject: {
              Data: 'Form contact our Site'
           },
           Body: {
               Text: {
                   Data: mensagem,
               }
            }
       }
    },
    function(err, data) {
        if (err) {
            console.log("ERROR="+err, err.stack); 
            context.done();
          } else {
            console.log("EMAIL SENT="+data);
            context.done();
          }
     });
}