web-dev-qa-db-ja.com

AWS Lambda内で非同期アクションを待つ方法は?

S3でアップロードされたファイルを処理しようとしています。 getObjectは非同期メイン関数が処理が完了する前に終了するため、AWSは3〜4秒でラムダを強制終了します。

さらに悪いことに、処理メソッドには非同期操作もあります-HTTP呼び出しを行います。

高レベルでは、私のコードは次のようになります。

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

Nodejsには非同期がありますが、Amazonには含まれていません。 require( 'async')またはrequire( 'sleep)の両方がエラーを引き起こします。

Lambdaタイムアウトは60秒に設定されていますが、3〜4秒で終了します。

44
st78

開発者の生活は絶えず変化しており、NodeJS 8はラムダ上にあります。これを見ている人は今チェックしてください:

Lambdaノード8.10とノード6.10の比較: https://aws.Amazon.com/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/

JS非同期の基本: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

さらに多くのAWS SDKの例: https://docs.aws.Amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html

.promise()メソッドのwtfの詳細は最初のリンクにあります: https://docs.aws.Amazon.com/AWSJavaScriptSDK/latest/AWS/Request.html#promise-property

これが基本的な例です(独自のラムダに貼り付けてみてください):

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'
};

上記の結果:

enter image description here

あなたが見ることができるように、それは私の機能を殺すことなく12秒待った:)

待機ごとに複数のことを行うには、次のようなPromise.all([])構文を使用します。

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'
}; 

以下のオリジナルの回答

私の手にはまったく同じ問題がありました。

問題は、javascriptイベントループが空なので、Lambdaが完了したと判断することです。

これが私がこの問題を解決した方法です。私はこれが理想的ではないことを認識しており、より良い方法があればいいのですが、a)ライブラリを追加したり、b)ラムダ呼び出しを調整したり、c)別の言語に切り替えたりしたくありませんでした。

一日の終わりに動作します。

    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

32
Neo

Async/awaitを使用する

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;
};
7
honkskillet

asyncは含まれていませんが、自分で追加できないわけではありません。パッケージをローカルに追加し(npm install async)、Lambda関数をアップロードする前にZipにnode_modulesフォルダーを含めるだけです。

Dev依存関係を個別に処理する場合(例:テスト、aws-sdk関数をローカルで実行するなど)、package.jsondevDependenciesの下に追加できます。また、コードの開発、テスト、デプロイ、およびプロモーションのプロセスを自動化する場合、これらの2つのリポジトリは非常に便利です。

ラムダをテスト、パッケージ化、デプロイするためのGruntルーチン

ラムダ関数を実行および展開するためのコマンドラインツール

7
Jose L Ugia

Lambdaは、一定の時間内に実行できるプログラムと考えてください。 (仮想)プロセッサがこれらの呼び出しをインターリーブする可能性があるため、非同期呼び出しを行うという事実は素晴らしいです。ただし、Lambdaプログラムの一部が、割り当てられた時間よりも完了するのに時間がかかる場合、実行は失敗します。それが妥協であり、Amazonがお金を稼ぐ方法です。より多くの時間や記憶を売ることによって。

これを修正するには、Lambda関数に割り当てられるメモリを増やすことができます。これにより、RAMだけでなく、仮想プロセッサの速度も向上します。もう1つできることは、タイムアウトを増やすことです。 AWS Lambdaで、最大512 MBのRAMと最大5分の処理時間が可能になりました。この投稿の時点で、これらの数値は変更されている可能性があるため、最新の制限について here を確認してください。この設定を変更するには、機能に移動してから設定を行い、最後に詳細設定に進みます。

5
SilentDirge

あなたのラムダ関数はcontext.done()で終わるべきだと思います。たとえば、次のように追加してみてください。

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

require('async'); packまたはrequire('sleep'); packも使用する場合は、次のようにZipファイルとして関数をアップロードする必要があります。

展開パッケージ(Node.js)の作成

Zipこの質問でも説明したように、フォルダーのすべてのコンテンツ:

Alexa JavascriptのAWS Lambda関数のMQTT

同期処理については、通常require('async');を使用できますが、次のようにasync.series関数を使用するだけです。

    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) {
        //...
    }
    //....
});

このようにして、lambda関数は同期して動作します。

お役に立てば幸いです。

4
pedro.olimpio

代わりにsynchronous呼び出しを行いたい場合があります。あなたは同じラムダ関数でファイルを処理しているようだからです。

何らかの理由でコールバックを取得したい場合;ラムダを直接呼び出すか、ラムダイベントを生成する何かを介して、それを行うことができます。ラムダ関数はステートレスであることに注意してください。そのため、必要なすべての情報を渡す必要があります。

1
Neil