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

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

ただいまの
回答率

89.10%

JavaScriptで、dynamodbからデータをscan->その結果を引数に関数を使いたい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,895

hiroga

score 79

前提・実現したいこと

AWSでLINEbotを作成しており、ユーザ全員にメッセージをマルチキャストしたいでうす。
dynamodbの中に入っているuseridの一覧をスキャン ->
jsonに代入してpostしたいのですが、どうしてもスキャンを待たずしてjsonのpostが始まってしまいます。

非同期処理について私なりに調べ、コードを工夫したのですがうまくいきませんでした。
どう書けば望み通りの処理ができるのか、ご教授いただけないでしょうか。

発生している問題・エラーメッセージ

メッセージ送信とデータのスキャンが逆転してしまいます。

(前半略)
13:39:40
2017-04-04T13:39:40.345Z    ea66573c-27aa-46a0-8c34-dffe44c07e69    2. Scan User and multicast message

13:39:40
2017-04-04T13:39:40.744Z    ea66573c-27aa-46a0-8c34-dffe44c07e69    Start sending message...

13:39:40
2017-04-04T13:39:40.744Z    ea66573c-27aa-46a0-8c34-dffe44c07e69    let send stat!

13:39:40
2017-04-04T13:39:40.746Z    ea66573c-27aa-46a0-8c34-dffe44c07e69    let send End!

13:39:41
2017-04-04T13:39:41.006Z    ea66573c-27aa-46a0-8c34-dffe44c07e69    onScan start

13:39:41
2017-04-04T13:39:41.006Z    ea66573c-27aa-46a0-8c34-dffe44c07e69    onScan succeeded.

13:39:41
2017-04-04T13:39:41.006Z    ea66573c-27aa-46a0-8c34-dffe44c07e69    This is message after Scan ->

13:39:41
2017-04-04T13:39:41.006Z    ea66573c-27aa-46a0-8c34-dffe44c07e69    { to: [ 'user12345', 'user67890' ], messages: [ { type: 'text', text: 'こんにちは!' } ] }

13:39:41
END RequestId: ea66573c-27aa-46a0-8c34-dffe44c07e69

該当のソースコード

'use strict';

// console.log("Start code!");

const https = require('https');
var AWS = require("aws-sdk");

AWS.config.update({region: "us-east-1",});
var docClient = new AWS.DynamoDB.DocumentClient();

var message = {};
var params = {};

let send = (data, callback) => {
  console.log("let send stat!");
  let body = JSON.stringify(data);

  let req = https.request({
    hostname: "api.line.me",
    port: 443,
    path: "/v2/bot/message/multicast",
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Content-Length": Buffer.byteLength(body),
      "Authorization": "Bearer " + process.env.CHANNEL_ACCESS_TOKEN
    }
  });

  req.end(body, (err) => {
    err && console.log(err);
    callback(err);
  });
  console.log("let send End!");
};

exports.handler = (event, context, callback) => {
    console.log("Start Handler!");
    console.log('EVENT:', JSON.stringify(event, null, 2));

    console.log("1. Generate Message Template");

    let content = event.Records[0];
    console.log("content ->");
    console.log(content);

    let sayThis = "こんにちは!";

    message = genNotification(sayThis, url);
    console.log("This is message ->");
    console.log(message);

    params = gen_scan_params();

    sendMessage4Scan(params,
        () => {send(message,
            () => {callback();
            });
        }
    );
};

// sendMethodはコールバック関数なのだから、スキャン後に呼ばれることを期待していたのですが...
function sendMessage4Scan(params,sendMethod){
    docClient.scan(params, onScan);
    console.log("Start sending message...");
    sendMethod();
}

function genNotification(sayThis, url){
    console.log("gen notify");
    message = {
        "to":[],
        "messages": [
          {
            "type": "text",
            "text": sayThis
          }
        ]
    };
    return message;
}

function gen_scan_params(){
    params = {
        TableName: process.env.TABLE_USERS,
        ProjectionExpression: "id"
    };
    return params;
}

function onScan(err, data) {
    console.log("onScan start");
    if (err) {
        console.error("Unable to scan the table. Error JSON:", JSON.stringify(err, null, 2));
    } else {
        console.log("onScan succeeded.");
        data.Items.forEach(function(user) {
            message.to.push(user.id);
        });
        console.log("This is message after Scan ->");
        console.log(message);

        // continue scanning if we have more movies, because
        // scan can retrieve a maximum of 1MB of data
        if (typeof data.LastEvaluatedKey != "undefined") {
            console.log("Scanning for more...");
            params.ExclusiveStartKey = data.LastEvaluatedKey;
            docClient.scan(params, onScan);
        }
    }
}

試したこと

非同期処理について調べ、コールバック関数を利用すれば順番に処理できると知ってそう書いてみました。しかし、やり方がよくないのか処理順が変わりません。

補足情報(言語/FW/ツール等のバージョンなど)

node.js 6.10
AWS lambda

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

もしもdocClient.scanが非同期な処理を行うのであれば、以下の関数はdocClient.scanの結果を待たずにsendMethod()を呼びに行ってしまいます。docClient.scanの結果を待ちたい場合は、sendMethodコールバックをonScanのコールバックとして渡す必要があります。(俗に言うCallback Hellというやつですね)

function sendMessage4Scan(params,sendMethod){
    docClient.scan(params, onScan);
    console.log("Start sending message...");
    sendMethod();
}

上記のコードに当てはめると以下のようなイメージでしょうか。

function onScan(err, data, sendMethod) {
  // 中略
 sendMethod(err, data);
}

function sendMessage4Scan(params, sendMethod) {
  docClient.scan(params, (err, data) => onScan(err, data, sendMethod));
}

ちなみにEventEmitterを使って書き直すと見通しの良いコードになると思います。

https://nodejs.org/api/events.html

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/04/05 07:41

    なるほど!ありがとうございます。
    動かすことができたので、理解の促進も兼ねて色々書き直してみます。

    EventEmitterというのは初耳でした。教えていただき感謝です。書き直し時に使ってみます!

    キャンセル

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

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