Help us understand the problem. What is going on with this article?

AWS SDK for JavaScript で Promiseを使う

More than 1 year has passed since last update.

2012年頃に スマホゲームのバックエンドを Node.js で開発した際には外部サーバやDBサーバとの通信を行うたびに コールバックを大量に書いて疲れていました。当時も非同期のライブラリがありましたが、当時は(も?)自分のJavaScriptレベルが低くて時間の制約から使うの諦めました。

今回、CloudWatchのカスタムメトリクスを登録する Lambda function を作るために Promise を勉強しました。

参考にしたサイト

断片的な How To ではないので、最も理解できたコンテツでした。

AWS SDK for JavaScript のバージョン

実装したもの

SQSに作成した2つのキューから、キュー数を取得して CloudWatchのカスタムメトリクスに登録します。

ソースコード

利用する環境変数は下の2つです。
1. queue : キュー名は環境変数 に JSONの配列で記載します。例えば ["my-sqs1","my-sqs2"] です。
2. env : メトリクスの Dimension に使います。

sqs_queues.js
'use strict'

// export env_name=dev

const AWS = require('aws-sdk');

AWS.config.update({region: 'ap-northeast-1'});

// Create CloudWatch service object
const SQS = new AWS.SQS({apiVersion: '2012-11-05'});
const CloudWatch = new AWS.CloudWatch({apiVersion: '2010-08-01'});

// 参照するSQSのキュー
const SOURCE_SQS_QUEUE_NAMES = JSON.parse(process.env.queue);
const SOURCE_SQS_METRICS_NAME = 'ApproximateNumberOfMessages';

// 合算したキュー数を書き込む CloudWatchのメトリクス
const DIST_METRICS_NAMESPACE = 'my-metrics-namespace';
const DIST_METRICS_NAME = 'my-custom-metrics';
const ENV_NAME = process.env.env_name; // dev, stage, prod ....


// メモ 2018年09月03日 時点
// 一度登録したカスタムメトリクスを コンソールやSDKで消すことはできない。
// 定められた保存期間を過ぎると削除される
// https://aws.amazon.com/jp/cloudwatch/faqs/

async function main(SOURCE_SQS_QUEUE_NAMES) {

    Promise.all(SOURCE_SQS_QUEUE_NAMES.map(function (queue_name) {
        // SQSのキューのURLを取得
        return queue_url(queue_name);
    })).then(function(urls){
        // キューを取得できたら、SQSの属性「ApproximateNumberOfMessages」を取得
        return Promise.all(urls.map(function (url) {
            return queue_number_of_messages(url);
        }))
    }).then(function(counts){
        // 各キューの ApproximateNumberOfMessages を合算して、メトリクスを登録
        let metrics_value = 0;
        counts.forEach(function (v) {
            console.log(v);
            metrics_value = metrics_value + v.size;
        });
        return put_metrics(metrics_value);
    }).then(function(putMetricsResponse){
        // メトリクスの登録に成功した
        console.log(JSON.stringify(putMetricsResponse));
    }).catch(function(err){
        // 上のいずれかでエラーが発生した。
        console.log(err);
    });
}

/**
 * SQSのキューURLを取得する
 * @param queue_name 取得する名
 * @returns {Promise<any>}
 */
function queue_url(queue_name) {
    const param = {
        QueueName: queue_name,
    };

    return SQS.getQueueUrl(param)
        .promise()
        .then(function (value) {
            return value.QueueUrl;
        });
}

/**
 * SQSのキュー数(ApproximateNumberOfMessages) を取得する
 * @param queue_url 取得するキューのURL
 * @returns {Promise<any>}
 */
function queue_number_of_messages(queue_url) {
    const param = {
        QueueUrl: queue_url,
        AttributeNames: [SOURCE_SQS_METRICS_NAME]
    };
    return SQS.getQueueAttributes(param)
        .promise()
        .then(function (value) {
            return {
                queue_url: queue_url,
                size: parseInt(value.Attributes.ApproximateNumberOfMessages),
            }}
        );
}

/**
 * カスタムメトリクスを登録する。登録されるメトリクスは下の通りになる
 * - Namespace: DIST_METRICS_NAMESPACE
 * - メトリクス名: DIST_METRICS_NAME
 * - ディメンジョン: Environment:環境名(実行時の環境変数 env_name の値 )
 * - 値: 引数 value
 *
 * @param value メトリクスの値。
 * @returns {Promise<any>}
 */
function put_metrics(value) {
    const metrics = {
        MetricData: [
            {
                Dimensions: [
                    {
                        Name: 'Environment',
                        Value: ENV_NAME
                    },
                ],
                MetricName: DIST_METRICS_NAME,
                Timestamp: new Date(),
                Value: "" + value,
                Unit: 'Count',
            },
        ],
        Namespace: DIST_METRICS_NAMESPACE
    };
    return CloudWatch.putMetricData(metrics)
        .promise()
        .then(function(data){
            return {metrics: metrics, put_metrics_result: data};
        });
}

main(SOURCE_SQS_QUEUE_NAMES);

処理イメージ

NodejsでPromise.png

やっていること

Promiseや非同期を調べていて、やり方を見つけられなかったのは、並列実行の結果を、次の直列実行に結果を渡す方法でした。

直列実行では Promise.then() の引数に指定した関数が return することで次の Promise.then() の引数に指定した関数が実行される時の引数になります。これと、 Promise.all() で並列実行する方法と組み合わせる方法に辿りつくまでに時間がかかりました。

辿り着いた方法は下の通りです。

  1. AWSへの処理は AWS SDKで Promiseを作成して、 then で実行される関数で直列実行で次に渡したい値を return する。 今回はqueue_url関数と queue_number_of_messages 関数です。
  2. 並列実行は 配列の各要素に対して行うので、 配列の map 関数で 前述の 関数を実行する Promise の配列を作る。作った配列は Promise.all() で並列実行します。
  3. メトリクスの登録は並列実行しないでの、put_metrics は単独で実行します。 Promise.all() を使いません。
  4. then() は結果を確認できるように putMetrics の結果を出力します。

エラー処理

Promiseの catch() は最後の1つだけです。途中でエラーになった場合は、すべてここで処理します。

akym03
株式会社ゆめみ でシステムエンジニアをやっています。
yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc
http://www.yumemi.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした