鉴于所有调用都是异步的,您如何在 lambda 中构建顺序 AWS 服务调用?

Posted

技术标签:

【中文标题】鉴于所有调用都是异步的,您如何在 lambda 中构建顺序 AWS 服务调用?【英文标题】:How do you structure sequential AWS service calls within lambda given all the calls are asynchronous? 【发布时间】:2015-02-27 06:40:11 【问题描述】:

我来自 java 背景,所以对 Lambda 所需的 javascript 约定有点陌生。

我有一个 lambda 函数,它旨在以特定顺序执行多个 AWS 任务,具体取决于前一个任务的结果。

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

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

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

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

【问题讨论】:

你找到解决办法了吗? 【参考方案1】:

我不了解 Lambda,但您应该查看节点 async library 作为对异步函数进行排序的一种方式。

没有您在问题中提到的深层嵌套问题,异步使我的生活变得更轻松,我的代码也更加有序。

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

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(); 
);

查看上面链接中的异步文档。串行、并行、瀑布流等有许多有用的功能。异步得到积极维护,看起来非常可靠。

祝你好运!

【讨论】:

【参考方案2】:

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

Five patterns to help you tame asynchronis javascript.

【讨论】:

我尝试的时候链接失效了,发现这个链接可能是文章的镜像:sking7.github.io/articles/389411742.html 编辑了答案以包含上面提供的评论镜像,因为答案中的原始内容现在正在分发恶意软件。【参考方案3】:

默认情况下 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();
          
     );

【讨论】:

【参考方案4】:

我喜欢@jonathanbaraldi 的回答,但我认为如果您使用 Promises 管理控制流会更好。 Q 库有一些方便的函数,如 nbind,它们有助于将节点样式回调 API (如 aws-sdk)转换为 Promise。

因此,在本例中,我将发送一封电子邮件,然后在收到电子邮件回复后,我将发送第二封电子邮件。这本质上就是要求的,依次调用多个服务。我正在使用 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();
    

【讨论】:

【参考方案5】:

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

// 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】:

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

    一个 Lambda 函数从 DynamoDB 获取一些东西,然后调用…… …调用 SNS 以获取端点,然后调用…的 Lambda 函数… …一个 Lambda 函数,通过 SNS 发送消息,然后调用… …写入 DynamoDB 的 Lambda 函数

所有这些函数都将前一个函数的输出作为输入。这当然是非常细粒度的,您可能会决定对某些呼叫进行分组。这样做至少可以避免 JS 代码中出现回调地狱。

(顺便说一句,我不确定 DynamoDB 与 Lambda 的集成情况如何。AWS 可能会为可以通过 Lambda 处理的记录发出更改事件。)

【讨论】:

【参考方案7】:

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

作为最后一个仅供参考 - AWS Lambda now supports .Net Core 它提供了开箱即用的这种干净的异步语法。

【讨论】:

【参考方案8】:

简答:

使用 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() 扩展添加到该函数(假设可用)。

对于遇到此线程并且实际上希望简单地将承诺推送到数组并等待整个数组完成(不考虑首先执行哪个承诺)的任何人,我最终得到了这样的结果:

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)

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

【讨论】:

以上是关于鉴于所有调用都是异步的,您如何在 lambda 中构建顺序 AWS 服务调用?的主要内容,如果未能解决你的问题,请参考以下文章

Lambda中的NodeJS异步未运行所有条目

如何使用无服务器框架从另一个 lambda 异步调用 lambda

如何让 Lambda 函数等待异步操作完成?

如何在返回集合的 lambda 中使用异步

如何在 AWS Lambda 中等待异步操作?

如何使用SAM通过API网关配置异步lambda调用?