C++幼女先輩

プログラミング成分多め

今更だけどAWS Lambdaのnode(Javascript)で Promiseしてみた

はじめに

node書いてるとコールバック地獄やですよね!
async/await使いたかったんだけど、AWS Lambdaなので、おとなしくPromiseしてみました

コールバック地獄

普通に書くとコールバック地獄になってしまう

'ues strict';
const AWS = require('aws-sdk');
const DDB = new AWS.DynamoDB();
const DDBDC = new AWS.DynamoDB.DocumentClient();

exports.handler = function(event, context, callback) {
  DDBDC.get( { TableName: "table1", Key:{"id": 1}} , (err1, data1) => {
    if (err1) {
      callback(err1);
      return;
    }
  
    DDBDC.get( { TableName: "table2", Key:{"id": 1}} , (err2, data2) => {
      if (err2) {
        callback(err2);
        return;
      }
      
      DDBDC.get( { TableName: "table3", Key:{"id": 1}} , (err3, data3) => {
        if (err3) {
          callback(err3);
          return;
        }

          // data1,data2,data3を使い データを操作
      });
    });
  });
}

おなじみのコールバック地獄
table1の検索後にtable2、table3と同期的に処理を行い結果を得られます
まず コールバック地獄辛い
そして table1、table2、table3 の処理を非同期で同時に行ってもよい
ただし、データを使う時には全部が終了していなければならない

ぱっと知ってる技術では 考えつかなかった

'ues strict';
const AWS = require('aws-sdk');
const DDB = new AWS.DynamoDB();
const DDBDC = new AWS.DynamoDB.DocumentClient();

exports.handler = function(event, context, callback) {
  DDBDC.get( { TableName: "table1", Key:{"id": 1}} , (err1, data1) => {
    if (err1) {
      callback(err1);
      return;
    }
  });
  
  DDBDC.get( { TableName: "table2", Key:{"id": 1}} , (err2, data2) => {
    if (err2) {
      callback(err2);
      return;
    }
  });
      
  DDBDC.get( { TableName: "table3", Key:{"id": 1}} , (err3, data3) => {
    if (err3) {
      callback(err3);
      return;
    }
  });

  // data1,data2,data3の同期を待ち データを操作

}

table1、table2、table3の検索が全て終わったのはどうやって調べましょうか(誰か教えて)

それ、Promise使えばいいよ

Promiseを使えば非同期関数を綺麗に(階層深くせず)コールバックを隠蔽できます
例えば最初の 各テーブルの読み込みを同期する場合は

'ues strict';
const AWS = require('aws-sdk');
const DDB = new AWS.DynamoDB();
const DDBDC = new AWS.DynamoDB.DocumentClient();


var get1 = (id) => {
    return DDBDC.get( { TableName: "table1", Key:{"id": id}}).promise();
};
var get2 = (id) => {
    return DDBDC.get( { TableName: "table2", Key:{"id": id}}).promise();
};
var get3 = (id) => {
    return DDBDC.get( { TableName: "table3", Key:{"id": id}}).promise();
};


exports.handler = function(event, context, callback) {

  get1(1)
  .then(get2(1))
  .then(get3(1))
  .then(()=>{
    // data1,data2,data3を使い データを操作
  })
  .catch( (err) => {
      callback(err);
  });

}

こんな感じで(動かしてないから間違えてるかもしれないけど) table1をまちtable2、table3と同期して
実行可能です

細かい実装はみてないけど、ぱっと見でいうと
AWSのライブラリにはだいたい promise()メソッドがある
中身は new Promise()で 関数からPromiseを作る便利メソッド
Promiseのメソッド thenは、関数の実行を待ち、終了後に引数の関数を呼び出す
結果的に get1の終了後にget2が呼ばれ、その後 get3が呼ばれる

では、お互いの終了を待たず get1、get2、get3を並列で実行し、全てが終了するのを待つ場合はどうだ?
並列で実行されるため、こっちの方が圧倒的にパフォーマンスが良い

promise all 使おう

'ues strict';
const AWS = require('aws-sdk');
const DDB = new AWS.DynamoDB();
const DDBDC = new AWS.DynamoDB.DocumentClient();


var get1 = (id) => {
    return DDBDC.get( { TableName: "table1", Key:{"id": id}}).promise();
};
var get2 = (id) => {
    return DDBDC.get( { TableName: "table2", Key:{"id": id}}).promise();
};
var get3 = (id) => {
    return DDBDC.get( { TableName: "table3", Key:{"id": id}}).promise();
};


exports.handler = function(event, context, callback) {


  Promise.all([get1(1), get2(1), get3(1)])
  .then ((results) => {
    var data1 = results[0];
    var data2 = results[1];
    var data3 = results[2];

    // data1,data2,data3を使い データを操作
   }).catch((err)=>{
        callback(err);
   });

}

Promise.all に配列としてpromiseを返す関数を渡せば
thenにて全ての終了を同期出来る
そして結果は 配列として帰ってくる
とてもすばらしい!

これでコールバックとはおさらば!

AWS Lambdaで使えるnodeのバージョンがあがれば、さらに綺麗にかける async/awaitも使えるんだけどね・・