web-dev-qa-db-fra.com

Comment attendre des actions asynchrones dans AWS Lambda?

J'essaie de traiter le fichier téléchargé dans S3. Puisque getObject est une fonction principale asynchrone se termine avant que le traitement ne soit terminé, et AWS tue lambda en 3-4 secondes.

Pire encore, la méthode de traitement comporte également des opérations asynchrones: elle effectue des appels http.

Sur un niveau élevé, mon code ressemble à:

exports.handler = function(event, context) {
    // Get the object from the event and show its content type
    var bucket = event.Records[0].s3.bucket.name;
    var key = event.Records[0].s3.object.key;
    var params = {
        Bucket: bucket,
        Key: key
    };
    s3.getObject(params, function(err, data) {
        if (err) {
             ...
        } else {
            processFile(data.Body.toString(), 0);
            console.log("ok");
        }
    });
    //need to wait here till processFile is done
};

processFile = function(content, start) {
  ... build url to call
  http.get(url, function(res) {  
    console.log("Got response: " + res.statusCode + ");
    processFile(content, start + 1);
  });
}

Je découvre qu'il y a async dans nodejs mais cela n'est pas inclus par Amazon; Les deux exigent ("async") ou exigent ("veille") des erreurs.

Le délai d'attente Lambda est configuré sur 60 secondes, mais il se termine dans 3-4 secondes.

32
st78

La vie d'un développeur change constamment et nous avons maintenant NodeJS 8 sur lambda. Pour ceux qui regardent cela maintenant, consultez:

Comparaison nœud Lambda 8.10 vs nœud 6.10: https://aws.Amazon.com/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/

Principes de base de JS async: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Encore plus d'exemples de sdk aws: https://docs.aws.Amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html

Les détails relatifs à la méthode .promise () dans le premier lien sont: https://docs.aws.Amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html#promise-property

Voici mon exemple de base (essayez de coller dans votre propre lambda):

exports.handler = async (event) => {    
    function wait(){
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve("hello"), 2000)
        });
    }
    
    console.log(await wait());
    console.log(await wait());
    console.log(await wait());
    console.log(await wait());
    console.log(await wait());
    console.log(await wait());
    
    return 'exiting'
};

Les rendements ci-dessus:

 enter image description here

Comme vous pouvez le voir, il a attendu 12 secondes sans tuer ma fonction :)

TODO plus d’une chose par attente, utilisez la syntaxe Promise.all ([]) comme ceci:

exports.handler = async (event) => {
    var uploadPromises = [];
    folder.files.forEach(file => {
        uploadPromises.Push( s3.putObject({
            Bucket: "mybucket",
            Key: file.name,
            Body: file.data
        }).promise());
    });

    await Promise.all(uploadPromises);
    return 'exiting'
}; 

_ {Réponse originale ci-dessous

J'ai eu exactement le même problème sur mes mains. 

Le problème est que la boucle d'événement javascript est vide, donc Lambda pense que c'est fait.

Voici comment j'ai résolu ce problème. Je réalise que ce n’est pas idéal et j’aimerais une meilleure solution, mais je ne voulais pas a) ajouter des bibliothèques, b) coordonner les invocations lambda ou c) passer à une autre langue. 

En fin de journée cela fonctionne.

    exports.handler = (event, context, callback) => {
        var response;
        var callBackCount;

        /*
        Ensures the javascript event loop is never empty.
        This is the key to keeping lambda from exiting early
        */
        setInterval(function(){}, 1000);

        /*
        Tell lambda to stop when I issue the callback.
        This is super important or the lambda funciton will always go until it hits the timeout limit you set.
        */
        context.callbackWaitsForEmptyEventLoop = false;
        
        //My way of determining when I'm done with all calls
        callBackCount = 0;
      
        //My info to return
        response = "";
        
        //Various functions that make rest calls and wait for a response
        asyncFunction1();
        asyncFunction2();
        asyncFunction3();

        //Same for asyncFunction 2 and 3
        function asyncFunction1(){
          response += callBackResponseForThisMethod;
      
          returnResponse();
        }

        function returnReponse(){
            callBackCount++;

            if(callBackCount == 3){
              //Lambda will stop after this as long as    context.callbackWaitsForEmptyEventLoop was set to false 
              callback(null, JSON.stringify(response));
            }
        }

    };

http://docs.aws.Amazon.com/lambda/latest/dg/nodejs-prog-model-context.html

18
Neo

async n'est pas inclus, mais cela ne signifie pas que vous ne pouvez pas l'ajouter vous-même. Ajoutez simplement le paquet localement (npm install async) et incluez le dossier node_modules dans votre fichier Zip avant de télécharger votre fonction Lambda.

Si vous souhaitez gérer les dépendances de développeurs séparément (par exemple: test, aws-sdk pour exécuter votre fonction localement, etc.), vous pouvez les ajouter sous devDependencies dans votre package.json. De plus, si vous souhaitez automatiser le processus de développement, de test, de déploiement et de promotion de votre code, ces deux mises en pension s'avéreront très utiles.

Grunt routine pour tester, empaqueter et déployer vos lambdas

Outil de ligne de commande pour exécuter et déployer vos fonctions lambda

7
Jose L Ugia

Pensez Lambda simplement comme un programme que vous pouvez exécuter dans un certain laps de temps. Le fait que vous fassiez des appels asynchrones est agréable, car le processeur (virtuel) peut éventuellement intercaler ces appels. Cependant, si une partie de votre programme Lambda prend plus de temps que le temps imparti, son exécution échouera. C'est le compromis que vous faites et c'est comment Amazon gagne de l'argent; en vous vendant plus de temps ou de mémoire.

Pour résoudre ce problème, vous pouvez augmenter la mémoire allouée à votre fonction Lambda. Cela augmente non seulement votre RAM mais également la vitesse de votre processeur virtuel. Une autre chose que vous pouvez faire est d’augmenter le délai d’attente. AWS Lambda vous autorise désormais jusqu'à 512 Mo de RAM et jusqu'à 5 minutes de temps de traitement. À compter de cette publication, ces chiffres peuvent avoir changé. Veuillez vérifier ici pour connaître les dernières limites. Pour changer ce paramètre, allez dans votre fonction, puis dans la configuration et enfin avancé.

5
SilentDirge

Je pense que votre fonction lambda devrait se terminer par un context.done () call. Par exemple, essayez de l'ajouter de cette façon:

s3.getObject(params, function(err, data) {
    if (err) {
         ...
        context.done("Error: " + err.stack);
    } else {
        processFile(data.Body.toString(), 0);
        console.log("ok");
        context.done(null, "success");
    }
});
5
rk2

Si vous souhaitez utiliser également require('async'); pack ou require('sleep'); pack, vous devez télécharger votre fonction en tant que fichier Zip comme ceci:

Création d'un package de déploiement (Node.js)

Zip tout le contenu du dossier, comme je l'explique également dans cette question:

Fonction MQTT dans AWS Lambda pour Alexa Javascript

A propos du traitement synchrone, vous pouvez utiliser require('async'); normalement, c’est juste utiliser la fonction async.series comme ceci:

    async.series([
    function(callback) {
        // to do the function 1
        callback();
    },
    function(callback) {
        // to do the function 2
        callback();
    },
    function(callback) {
        // to do the function 3
        callback();
    }
], function(err) {
    // to do the function if any error happens...

    if (err) {
        //...
    }
    //....
});

De cette façon, la fonction lambda fonctionnera de manière synchrone. 

J'espère vous aider.

4
pedro.olimpio

Utiliser async/wait

let AWS = require('aws-sdk');
let lambda = new AWS.Lambda();
let data;
exports.handler = async (event) => {
    try {
        data = await lambda.getAccountSettings().promise();
    }
    catch (err) {
        console.log(err);
        return err;
    }
    return data;
};
2
honkskillet

Vous voudrez peut-être faire un appel synchronous à la place; puisque vous semblez traiter votre fichier dans la même fonction lambda.

Si, pour une raison quelconque, vous souhaitez obtenir un rappel; vous pouvez le faire en invoquant directement lambda ou via quelque chose qui produirait un événement lambda. Notez que les fonctions lambda sont supposées être sans état; vous devez donc transmettre toutes les informations nécessaires.

0
Neil