45
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AWSのLambdaでasync / awaitを使う

Last updated at Posted at 2018-04-30

LambdaのNode.js v8.10対応

2018/4/2からAWSのLambdaがNode8に対応しました。
(東京リージョンも対応しています)

AWS Lambda が Node.js v8.10 をサポート

特にLambdaに関連した変更点としてうれしいのは、async / awaitが使えるようになったという点だと思います。
(パフォーマンスも向上する1らしいですが未検証です)

async / await を使うメリット

Node.jsは非同期処理を特徴としている為、コールバック地獄が待ち受けていました。(しかもLambdaの場合は、この非同期処理の恩恵があまり無いのがまた悲しい話で。。。)

これを回避する一つの方法としてPromiseを使う方法がありました。ただ、Promiseもチェーンが多くなったり分岐があったり例外処理が入ってくると、コールバック地獄よりはかなりマシとはいえ、読みにくくなってしまいます。

そこでasync / awaitを使うと、非同期処理部分を同期処理の様に記述することができるので、ソースがとても読みやすくなるというメリットがあります。

コード例

では、実際にasync / awaitが威力を発揮するLambda特有のコードの例を3つ取り上げます。

KMS

KMSはDBのパスワードなどの機密情報を暗号化しておけるものです。
この暗号化した環境変数を復号する際のテンプレートコードをasync / awaitを使って書き直すと以下のコードになります。

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

const encrypted = process.env['PASSWORD'];
let decrypted;


function processEvent(event, context, callback) {
    // TODO handle the event here
}

exports.handler = async (event, context, callback) => {
    if (decrypted) {
        processEvent(event, context, callback);
    } else {
        // Decrypt code should run once and variables stored outside of the function
        // handler so that these are decrypted once per container
        const kms = new AWS.KMS();
        
        try {
            const params = { CiphertextBlob: new Buffer(encrypted, 'base64') };
            const data = await kms.decrypt(params).promise();
            decrypted = data.Plaintext.toString('ascii');
            processEvent(event, context, callback);
        } catch (err) {
            console.log('Decrypt error:', err);
            callback(err);
        }        
    }
};

変更点

元のコード(コンソール上で「コード」を選択した時に出てくるテンプレート)からの変更箇所は以下の点です。

  • exports.handlerにセットするメソッドにasyncが付いている
  • kms.decryptawaitが付いている
  • kms.decryptにコールバック関数を設定せずにPromiseを返している
    • 成功時は返されたPromiseからデータを取得している
    • 失敗時はtry ~ catchでエラー内容を取得している

特にコールバック関数部分が無くなった為、処理が同期的に書かれていて流れが追いやすくなった上に、かなりすっきりとしたコードに変わっています。

S3

次はS3にファイルを並列で同時にアップロードする場合のコード例です。

const AWS = require('aws-sdk');
const s3 = new AWS.S3({region: 'ap-northeast-1'});

try {
    // 同時実行できるように先にPromiseの配列を作成
    // (メモリ不足になるほどの容量・ファイル数はこない想定)
    const writePromises = Object.keys(event).map(function(key) {
        // jsonのキーがファイル名、値をファイルの内容のjson形式にする
        const params = {
            Bucket: 'test-bucket',
            Key: key + '.json',
            Body: JSON.stringify(event[key]),
            ContentType: 'application/json',
            ACL: 'public-read'                  // 読み取り可で公開
        };

        // S3へアップロードするPromiseを返す
        return s3.upload(params).promise();
    });

    // 並列でPromiseを全て実行する     
    await Promise.all(writePromises);
} catch (err) {
    // S3への書き込みに失敗した場合
    console.log('S3 upload error:', err);
}

まずはjsonの中身を走査してS3へのアップロードを行うPromiseの配列を作成します。その後、その配列をPromise.allに渡して、並列で同時実行させています。途中でどれか一つでも失敗した場合は、catch部分が呼び出されます。

この例で、並列で書き込んでいるのは少しでも実行時間を短くする為です。
(外部のAPIやリソースの取得のように時間がかかる場合は効果的です)

DynamoDB

最後は、DynamoDBへシリアルでデータを書き込む場合のコード例です。
シリアルの場合、1件の書き込みが終わってから次の書き込みを行います。

const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient({region: "ap-northeast-1"});

for (let key in event) {
    // jsonのキーがid(プライマリキー)、値をcontentにする
    const params = {
        TableName: 'test',
        Key: {
            'id': key
        },
        UpdateExpression: 'set content = :val',
        ExpressionAttributeValues: {
            ':val': event[key]
        }
    };
    
    try {
        // DBを更新する
        await dynamo.update(params).promise();
    } catch (err) {
        // DBの更新に失敗した場合
        console.log('DynamoDB update error:', err);
    }
}

今回は、シリアルなのでjsonの中身を走査してそのまま1件ずつDBに更新をかけています。
通常、コールバックでこういった処理を書こうとすると、再帰的なコードが出てきてしまいますが、async / awaitを使うと自然なループだけで処理を書くことができます。

DynamoDBでは、一つ前のS3と違って並列で同時に書き込むと書き込みキャパシティを余分に消費してしまうので、逐次的に書き込む方法を選んでいます。

まとめ

async / awaitを使うとコールバック地獄から解放されます。
すでにPromiseを使っている場合でもソースが読みやすくなります。

特にNode8にしたからといって弊害もなさそうなので、今後新しいAPIを作る時はNode8を選択するのが良さそうです。

  1. 参考リンクAWS Lambda Node.js 8 support: what it changes for serverless developers

45
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?