webentwicklung-frage-antwort-db.com.de

Wie strukturieren Sie sequentielle AWS-Serviceaufrufe innerhalb von Lambda, wenn alle Aufrufe asynchron sind?

Ich komme aus einem Java-Hintergrund, daher sind die für Lambda erforderlichen Javascript-Konventionen ein wenig neu.

Ich habe eine Lambda-Funktion, die je nach Ergebnis der vorherigen Aufgabe mehrere AWS-Aufgaben in einer bestimmten Reihenfolge ausführen soll.

Angesichts der Tatsache, dass jede Aufgabe ihre Ergebnisse asynchron meldet, frage ich mich, ob der richtige Weg sicherstellt, dass sie alle in der richtigen Reihenfolge ablaufen und die Ergebnisse einer Operation für den Aufruf der nächsten Funktion verfügbar sind.

Es sieht so aus, als müsste ich jede Funktion im Rückruf der vorherigen Funktion abrufen, aber so wird eine Art tiefes Verschachteln und die Frage, ob dies der richtige Weg ist, dies zu tun.

Beispielsweise erfordert eine dieser Funktionen ein DynamoDB-getItem, gefolgt von einem Aufruf von SNS, um einen Endpunkt abzurufen, gefolgt von einem SNS-Aufruf, um eine Nachricht zu senden, gefolgt von einem DynamoDB-Schreibvorgang.

Was ist der richtige Weg, dies in Lambda-Javascript zu tun und all diese Asynchronität zu berücksichtigen?

16
user1023110

Ich mag die Antwort von @jonathanbaraldi, aber ich denke, es wäre besser, wenn Sie den Kontrollfluss mit Promises verwalten. Die Q-Bibliothek verfügt über einige praktische Funktionen wie nbind, mit denen Knoten-Callback-APIs wie aws-sdk in Versprechen umgewandelt werden können.

In diesem Beispiel sende ich eine E-Mail, und sobald die E-Mail-Antwort zurückkommt, sende ich eine zweite E-Mail. Dies wurde im Wesentlichen verlangt, indem mehrere Services nacheinander aufgerufen wurden. Ich verwende die then-Methode von Versprechungen, um diese auf vertikal lesbare Weise zu verwalten. Verwenden Sie auch catch, um Fehler zu behandeln. Ich denke, es liest sich viel besser, indem man einfach Callback-Funktionen verschachtelt.

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 protected]'];
    var from = '[email protected]';

    // 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();
    }
}
4
nackjicholson

Ich kenne Lambda nicht, aber Sie sollten in den Knoten async-Bibliothek schauen, um asynchrone Funktionen zu sequenzieren.

async hat mir das Leben und den Code viel einfacher gemacht, ohne das tiefe Schachtelungsproblem, das Sie in Ihrer Frage erwähnt haben.

Ein typischer asynchroner Code könnte folgendermaßen aussehen:

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

Schauen Sie sich das async doco unter dem Link oben an. Es gibt viele nützliche Funktionen für serielle, parallele, Wasserfälle und viele mehr. Async wird aktiv gewartet und scheint sehr zuverlässig zu sein.

viel Glück!

2
RoyHB

Eine sehr spezifische Lösung, die mir einfällt, ist die Kaskadierung von Lambda-Anrufen. Zum Beispiel könnten Sie schreiben:

  1. Eine Lambda-Funktion erhält etwas von DynamoDB und ruft dann…
  2. … Eine Lambda-Funktion, die SNS anruft, um einen Endpunkt zu erhalten, und dann…
  3. … Eine Lambda-Funktion, die eine Nachricht über SNS sendet und dann aufruft…
  4. … Eine Lambda-Funktion, die in DynamoDB schreibt

Alle diese Funktionen übernehmen die Ausgabe der vorherigen Funktion als Eingabe. Dies ist natürlich sehr feinkörnig, und Sie können sich entscheiden, bestimmte Anrufe zu gruppieren. Wenn Sie dies auf diese Weise tun, vermeiden Sie zumindest den Rückruf in Ihrem JS-Code.

(Als Randbemerkung bin ich nicht sicher, wie gut DynamoDB in Lambda integriert ist. AWS kann Änderungsereignisse für Datensätze ausgeben, die dann über Lambda verarbeitet werden können.)

1
awendt

Ich möchte die folgende Lösung anbieten, die einfach eine verschachtelte Funktionsstruktur erstellt.

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

Dies bedeutet, dass Sie alle Variablen für den Funktionsaufruf kopieren, den Sie erstellen möchten, und sie dann in den vorherigen Aufruf verschachtelt. Sie möchten Ihre Ereignisse rückwärts planen. Dies ist im Grunde dasselbe wie eine Pyramide von Rückrufen, funktioniert jedoch, wenn Sie die Struktur oder die Anzahl der Funktionsaufrufe nicht im Voraus kennen. Sie müssen die Funktion in einen Abschluss einschließen, damit der korrekte Wert übernommen wird.

Auf diese Weise kann ich AWS-Serviceaufrufe so sequenzieren, dass sie 1-2-3 gehen und mit dem Schließen des Kontexts enden. Vermutlich könnten Sie es anstelle dieser Pseudo-Rekursion auch als Stapel strukturieren.

0
Ben

Ich habe gerade diesen alten Faden gesehen. Beachten Sie, dass zukünftige Versionen von JS dies verbessern werden. Schauen Sie sich die Syntax ES2017 async/await an, die ein asynchrones, verschachteltes Callback-Chaos in einen sauberen, synchronen Code verschlüsselt. Nun gibt es einige Polyfills , die Sie bereitstellen können Diese Funktionalität basiert auf der ES2016-Syntax.

Als letztes FYI - AWS Lambda unterstützt jetzt .Net Core , das diese saubere asynchrone Syntax aus der Box liefert.

0
Froyke

Kurze Antwort:

Verwenden Sie Async/Await - und rufen Sie den AWS-Dienst (z. B. SNS) mit der Erweiterung .promise () an, um aws-sdk anzuweisen, anstelle der auf Rückruf basierenden Version die Versprechungs-Version dieser Dienstfunktion zu verwenden. 

Da Sie sie in einer bestimmten Reihenfolge ausführen möchten, können Sie Async/Await verwenden, vorausgesetzt, die übergeordnete Funktion, von der Sie sie aufrufen, ist selbst asynchron.

Zum Beispiel:

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

Der wichtige Teil ist das .promise () am Ende dort. Vollständige Dokumente zur Verwendung von aws-sdk in asynchroner/versprechender Weise finden Sie hier: https://docs.aws.Amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html

Um eine weitere aws-sdk-Task auszuführen, würden Sie in ähnlicher Weise wait und die Erweiterung .promise () zu dieser Funktion hinzufügen (vorausgesetzt, dass verfügbar ist).

Für jeden, der in diesen Thread läuft und eigentlich nur Push-Versprechen in ein Array schieben und warten möchte, bis das WHOLE-Array beendet ist (ohne Rücksicht auf das, was zuerst ausgeführt wird), habe ich so etwas wie folgt:

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)

Hoffe, das hilft jemandem, der zufällig über Google darüber stolpert, wie ich es tat!

0
Necevil

Ich habe diesen Artikel gefunden, der die Antwort in nativen Javascript zu haben scheint.

Fünf Muster, mit denen Sie Asynchronis-Javascript zähmen können.

0
user1023110