web-dev-qa-db-fra.com

Comment structurez-vous les appels de service AWS séquentiels dans lambda, étant donné que tous les appels sont asynchrones?

Je viens d'un environnement Java, donc un débutant sur les conventions Javascript nécessaires pour Lambda.

J'ai une fonction lambda qui est destinée à effectuer plusieurs tâches AWS dans un ordre particulier, en fonction du résultat de la tâche précédente.

Étant donné que chaque tâche rapporte ses résultats de manière asynchrone, je me demande si vous devez vous assurer que tous les résultats sont corrects et que les résultats d'une opération sont disponibles pour invoquer la fonction suivante.

Il semble que je doive invoquer chaque fonction dans le rappel de la fonction précédente, mais cela semble être une sorte de nidification en profondeur et en se demandant si c'est la bonne façon de procéder.

Par exemple, l’une de ces fonctions nécessite un getItem DynamoDB, suivi d’un appel à SNS pour obtenir un point de terminaison, suivi d’un appel SNS pour l’envoi d’un message, suivi d’une écriture DynamoDB.

Quelle est la bonne façon de faire cela dans lambda javascript, en tenant compte de toute cette asynchronicité?

16
user1023110

J'aime la réponse de @jonathanbaraldi mais je pense que ce serait mieux si vous gérez le flux de contrôle avec Promises. La bibliothèque Q possède des fonctions pratiques comme nbind qui aident à convertir les API de rappel de style de nœud comme aws-sdk en promesses.

Donc, dans cet exemple, j'enverrai un courrier électronique et, dès que la réponse par courrier électronique nous parviendra, j'enverrai un deuxième courrier électronique. C'est essentiellement ce qui a été demandé, appelant plusieurs services en séquence. J'utilise la méthode des promesses then pour gérer cela de manière lisible verticalement. Utiliser également catch pour gérer les erreurs. Je pense qu'il se lit beaucoup mieux simplement imbriquant des fonctions de rappel.

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

Je ne connais pas Lambda, mais vous devriez regarder dans le noeud bibliothèque async comme moyen de séquencer les fonctions asynchrones.

async m'a rendu la vie beaucoup plus facile et mon code beaucoup plus ordonné sans le problème de nidification en profondeur que vous avez mentionné dans votre question.

Le code async typique peut ressembler à:

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

Consultez le document async sur le lien ci-dessus. Il existe de nombreuses fonctions utiles pour les séries, les parallèles, les chutes d'eau et bien d'autres. Async est activement maintenu et semble très fiable.

bonne chance!

2
RoyHB

Une solution très spécifique qui me vient à l’esprit est la cascade d’appels Lambda. Par exemple, vous pourriez écrire:

  1. Une fonction Lambda obtient quelque chose de DynamoDB, puis appelle…
  2. … Une fonction Lambda qui appelle SNS pour obtenir un terminal, puis appelle…
  3. … Une fonction Lambda qui envoie un message via SNS, puis appelle…
  4. … Une fonction Lambda qui écrit dans DynamoDB

Toutes ces fonctions prennent en sortie la sortie de la fonction précédente. Ceci est bien sûr très fin, et vous pouvez décider de regrouper certains appels. En procédant ainsi, vous éviterez au moins un rappel dans votre code JS.

(Remarque: je ne sais pas dans quelle mesure DynamoDB s'intègre bien avec Lambda. AWS pourrait émettre des événements de modification pour des enregistrements pouvant ensuite être traités via Lambda.)

1
awendt

Je voudrais offrir la solution suivante, qui crée simplement une structure de fonction imbriquée.

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

Cela permet de copier toutes les variables pour l'appel de fonction que vous souhaitez effectuer, puis de les imbriquer dans l'appel précédent. Vous voudrez planifier vos événements en arrière. C'est en fait la même chose que de faire une pyramide de callbacks, mais fonctionne quand vous ne connaissez pas à l'avance la structure ou le nombre d'appels de fonction. Vous devez envelopper la fonction dans une fermeture pour que la valeur correcte soit copiée.

De cette manière, je suis capable de séquencer les appels de service AWS de manière à ce qu'ils passent 1-2-3 et finissent par fermer le contexte. Vraisemblablement, vous pourriez aussi le structurer comme une pile au lieu de cette pseudo-récursion.

0
Ben

Je viens de voir ce vieux fil. Notez que les futures versions de JS vont améliorer cela. Examinez la syntaxe ES2017 async/wait qui rationalise un désordre de rappel imbriqué asynchrone en un code pur à la synchronisation. Il existe maintenant quelques polyfills qui peuvent vous fournir cette fonctionnalité Syntaxe ES2016.

En guise de conclusion, AWS Lambda prend désormais en charge .Net Core , qui fournit cette syntaxe async propre, prête à l'emploi.

0
Froyke

Réponse courte:

Utilisez Async/Await - et appelez le service AWS (SNS par exemple) avec une extension .promise () pour indiquer à aws-sdk d'utiliser la version promis de cette fonction de service à la place de la version basée sur le rappel. 

Puisque vous voulez les exécuter dans un ordre spécifique, vous pouvez utiliser Async/Await en supposant que la fonction parent à partir de laquelle vous les appelez est elle-même asynchrone.

Par exemple:

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

La partie importante est le .promise () à la fin là. Des documents complets sur l'utilisation d'aws-sdk de manière asynchrone/promise peuvent être trouvés ici: https://docs.aws.Amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html

Pour exécuter une autre tâche aws-sdk, vous devez également ajouter wait et l'extension .promise () à cette fonction (en supposant que celle-ci soit disponible).

Pour tous ceux qui rencontrent ce fil et qui cherchent en fait à pousser simplement les promesses vers un tableau et à attendre la fin de ce tableau TOUT (sans tenir compte du fait que la promesse est exécutée en premier), j'ai obtenu quelque chose comme ceci:

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)

J'espère que cela aidera quelqu'un qui trébuche de façon aléatoire via google comme je l'ai fait!

0
Necevil

J'ai trouvé cet article qui semble avoir la réponse en javascript natif.

Cinq modèles pour vous aider à apprivoiser javascript asynchronis.

0
user1023110