質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.97%

DynamoDBに複数の項目を追加するプログラムでcontext.done()をコールするタイミングがわかりません

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,366

akabei

score 19

AWSのLambdaでS3にアップロードされたJSONファイルを読み込んで
その内容をDynamoDBへ書き込むプログラムを作成しています。

JSONファイルには複数のデータがありこれを全てDynamoDBへ書き込みます。

以下が作成したソースコードとJSONファイルの内容です。

console.log('Loading function ...');

var aws = require('aws-sdk');
var s3 = new aws.S3({ apiVersion: '2006-03-01' });
var dynamo = new aws.DynamoDB({region: 'ap-northeast-1'});

//読み込んだJSONをDynamoDBのパラメータ形式に変換
function getItemList(table) {
    var items = [];
    for (var i = 0; i < table.length; i++) {
        var item = {};
        var record = table[i];
        console.log(record);
        for (var column in record) {
            item[column] = {'S': record[column]};
        }
        items[i] = item;
    }
    return items;
}

exports.handler = function(event, context) {
    //console.log('Received event:', JSON.stringify(event, null, 2));
    //S3からJSONファイルを読み込む
    var bucket = event.Records[0].s3.bucket.name;
    var key = event.Records[0].s3.object.key;
    console.log('Bucket:', bucket, ' Key:', key);
    s3.getObject({Bucket: bucket, Key: key}, function(err, data) {
        if (err) {
            console.log(err);
            context.fail('S3 object get error!! ' + err.code + ":" + err.message);
        }
        else {
        //テーブル名
            var tablename = 'test';

            //読み込んだJSONをDynamoDBのパラメータ形式に変換
            var content = JSON.parse(String(data.Body));
            console.log('Table:', tablename, ' Count:', content[tablename].length);
            var items = getItemList(content[tablename]);
            for (var i = 0; i < items.length; i++) {
                var params = {
                    TableName: tablename,
                    Item: items[i]
                };
                //DynamoDBへ書き込む
                dynamo.putItem(params, function (err, data) {
                    if (err) {
                        console.log(err);
                        context.fail('DynamoDB put error!! ' + err.code + ':' + err.message);
                    }
                    else { 
                        console.log('data uploaded successfully.');
                        //★1)doneを呼び出すタイミングは?
                        //context.done();
                    }
                }); //★2)ワーニング: Don't make functions within a loop.
            }
        }
    });
}
{
    "test": [
        {
            "id": "1",
            "key": "000001",
            "data1": "abcdefg"
        },
        {
            "id": "2",
            "key": "000002",
            "data1": "abcdefg",
            "data2": "ABCDEFG"
        },
        {
            "id": "3",
            "key": "000003",
            "data1": "abcdefg",
            "data2": "ABCDEFG",
            "data3": "1234567"
        }
    ]
}

動作するのは確認できたのですが、わからない点が2つあります。

★1) DynamoDBへ書き込む際、複数回putItem()をコールしたときcontext.done()をコールするタイミングがわからない。
★2) putItem()のfunctionでワーニングが発生してしまいます。functionはループ内に作成してはいけないようですがどのように修正したらよいでしょうか?

context.done()はコールしなくてもよいのか?全ての書き込みが終了したことを判断する方法が別にあるのでしょうか?
functionは別に分けてみましたがcontextが参照できなくなってしまいました。

BatchWriteItem()で一度にまとめて書き込めることは分かったのですが
今回のように引数にfunctionをもつ関数を複数回コールしたいときどのようにプログラムするのがいいのでしょうか?

同様のプログラムを作成された方がいましたら
教えていただけると助かります。

追記)
★2はコードをテキストファイルにコピーして貼り付けなおしたところワーニングは消えました。
これでいいのかわかりませんが・・・。
引き続き★1について何かわかることがあれば教えて下さい。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

DynamoDBについての知識はないです(この質問でしったくらい)が、JavaScriptの知識だけで回答してみます

まず、コールの必要性ですが、ググると Amazon の公式ぽいところに

Important
Lambda 関数の実行を正しく終了させるためには、context.succeed()、context.fail()、または context.done() メソッドを呼び出す必要があります。どのメソッドも呼び出していない場合、Node.js のイベントキューが空になるか、または Lambda 関数がタイムアウトするまで、Lambda 関数は実行を続けます。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-context.html

とあったので、success も fail も呼ばないときは呼び出したほうがいいのでしょう
実行終わったら Node.js が終了するから不要な場合もあるかもですけど、大抵の場合はそうじゃないと思います

呼び出す場合ですが、これまたぐぐってみると、putItem の後のコールバックの中で context.done を呼び出している例がいくつかありました
putItem を複数実行している場合は全部のコールバックが終わったときに、context.done() を呼べばいいと思います

for 文の前に length を保存しておいて 各コールバックの中で 1 ずつ減らしていき、 0 になったら最後の処理を呼び出すというのが簡単な方法かと思います

ですが、こんなときの Promise.all なのかなと思ったので for (var i = 0; i < items.length; i++) { のところを Promise 化してみました

Promise.all(items.map(item => {
    return new Promise((resolve, reject) => {
        var params = {
            TableName: tablename,
            Item: item
        };
        //DynamoDBへ書き込む
        dynamo.putItem(params, function (err, data) {
            if(err) {
                console.log(err);
                context.fail('DynamoDB put error!! ' + err.code + ':' + err.message);
            } else {
                console.log('data uploaded successfully.');
                resolve(data);
            }
        });
    });
})).then(_ => {
    context.done();
})

シンタックスしかチェックしてないです

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/03/30 16:28

    回答ありがとうございます。

    教えていただいた方法で試したり調べたことで不明点が明確になりました。

    ・Promiseを使用してcontext.done()を呼び出せばいい。
    ・Promiseの使い方が理解できていない。
    ・Lambdaで直接コードを記述するとPromiseが使用できないので、外部モジュールをzipにまとめてアップロードする必要がある。
    ・npmコマンドで外部モジュールをパッケージ化できる。

    Node.js、Javascript、AWSとまだまだ勉強不足のまま手をつけてしまったのが良くなったようです。

    教えていただいた内容から
    参考になるサンプルコードも見つけることもできましたので
    基本的なところから勉強しなおして見ようと思います。

    大変参考になりました。ありがとうございます。

    キャンセル

  • 2016/03/31 11:01

    以下のコードで動作することを確認できました!

    ```JavaScript
    new Promise(function(resolve, reject){
    for (var i = 0; i < items.length; i++) {
    //console.log(items[i]);
    var params = {
    TableName: tablename,
    Item: items[i]
    };
    //DynamoDBへ書き込む
    dynamo.putItem(params, function (err, data) {
    if (err) {
    reject(err);
    }
    else {
    resolve(data);
    }
    });
    }
    }).then(function(data) {
    console.log("DynamoDB put success.", data);
    context.done();
    }).catch(function(err) {
    console.log(err);
    context.fail("DynamoDB put error!! " + err.code + ":" + err.message);
    });
    ```

    Promise.allはまだ勉強不足で使用していませんがこれから勉強して使えるようにしたいと思います。
    あらためてありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.97%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる