[祝 LambdaのNode v4対応記念]AWS SDK for Node.jsの処理をPromiseを使って書いてみた

LambdaでNodev4.3がサポートされました!

[AWS Lambda Supports Node.js 4.3](https://aws.amazon.com/jp/about-aws/whats-new/2016/04/aws-lambda-supports-node-js-4-3/?sc_channel=sm&sc_campaign=launches_2016&sc_publisher=tw_go&sc_content=lambda_supports_nodejs_4_3&sc_country=global&sc_geo=global&sc_category=lambda&adbsc=social_launches_20160407_60266766&adbid=718162644394311681&adbpl=tw&adbpr=66780587)

[AWS LambdaでNode.js 4.3.2が利用可能になりました](http://aws.typepad.com/sajp/2016/04/node-js-4-3-2-runtime-now-available-on-lambda.html)

あまり詳しい訳ではないのですが、Node v4.3ではES6のサポートが大きな特徴として挙げられます。(Arrow Function,let,const,Promise,Class
などなど)

ES6の機能は数多くあるのですが、今回はその中でも非同期処理を簡単に書けるようになるPromiseについて実際にAWS SDKを使ってみてどのように書けるか確認したのでその時のメモです。

検証したコードはNode.jsとしての挙動ではありますが、LambdaでAWS SDKをPromiseを使って書く場合に参考になるかと思います。

なお、Promiseの詳細については書いていないので別途以下などを参考のこと。

[JavaScript Promiseの本](https://azu.github.io/promises-book/)

# とりあえず書いてみる

## 普通にコールバックで書く

AWS SDKのページにも書いてあるよくある書き方ですね。

```hoge.js
var AWS = require('aws-sdk');
AWS.config.update({region: 'ap-northeast-1'});

var ec2 = new AWS.EC2();

ec2.describeInstances("", function(err, data) {
  if (err) console.log(err);
  console.log(data);
});
```

## コールバックをPromiseでラップする

コールバック関数をPromiseでラップする関数(describeInstancesAsync)を作って置き換えてみました。

```hoge.js
var AWS = require('aws-sdk');
AWS.config.update({region: 'ap-northeast-1'});

var ec2 = new AWS.EC2();

function describeInstancesAsync(param) {
  return new Promise(function (resolve, reject) {
    ec2.describeInstances(param, function(err, data) {
      if (err) reject(err);
      resolve(data);
    });
  });
}

describeInstancesAsync("").then(function(data) {
  // 成功
  console.log(data);
}).catch(function (error) {
  // 失敗
  console.log(error);
});
```

これでPromiseを使って同じ結果が得られます。
が、これは記述量が増えてますし、利用したいAPIごとにこれを書くのはかなり面倒。。。。

## AWS.request.promise()を使う

よくよくAWS SDK for Node.jsのドキュメントを見るとPromiseのサポートしているよという事が書いてありました。

[Support for Promiss](https://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-making-requests.html#Support_for_Promises)

ということでやってみます

```hoge.js
var AWS = require('aws-sdk');
AWS.config.update({region: 'ap-northeast-1'});

var ec2 = new AWS.EC2();

ec2.describeInstances().promise().then(function(data) {
  // 成功
  console.log(data);
}).catch(function (error) {
  // 失敗
  console.log(error);
});
```

ec2.describeInstances()などの非同期処理のメソッドで返却されるAWS.Requestオブジェクトにはpromise()メソッドが用意されており、このメソッドの返却値がPromiseオブジェクトなのでこれを普通に使えばOKです。

# Promiseを使って並列非同期処理を書いてみる

1つの非同期処理をコールバック関数からPromiseに置き換えてもあまり嬉しくありません。

Promiseは複数の非同期処理を連続で実行する時にすごく便利になります。

試しに以下のS3の処理をPromiseを使って書いてみます

+ listBuckets()メソッドで全てのバケット名を取得
+ 取得したそれぞれのバケット名に対してlistObjects()メソッドを並列に呼び出す
+ 全てのlistObjects()メソッドが完了したらそれぞれのバケットの詳細を表示する

```hoge.js
var AWS = require('aws-sdk');
AWS.config.update({region: 'ap-northeast-1'});

var s3 = new AWS.S3();

s3.listBuckets().promise().then(function(data) {
  // listBucketsメソッドが成功した場合に呼び出される
  var array = [];
  data.Buckets.forEach(function(bucket) {
    var params = {
      Bucket: bucket.Name
    };
    array.push(s3.listObjects(params).promise());
  });

  // 各バケット名のlistObjectsメソッドを並行で呼び出す
  return Promise.all(array);
}).then(function(dataArray) {

  // 全てのlistObjectsメソッドが正常終了したら呼び出される
  dataArray.forEach(function(data) {
    console.log(data);
  });
}).catch(function(error) {
  // listBucketsもしくはlistObjectsメソッドのいずれかが失敗した場合に呼び出される
  console.log(error);
});
```

コールバック関数を使った場合と比較し、Promiseを使った場合の利点を以下に書きます。

+ ネストが深くなることを避ける
+ 非同期処理のエラーハンドリングをまとめて書くことができる
+ 並行した非同期処理を簡単に書ける

## ネストが深くなることを避ける

普通にコールバック関数を使うとlistBuckets()メソッドが呼び出された後のlistObjects()メソッドはそれぞれネストしてしまい、どんどん階層が深くなってしまうという問題点があります。(コールバック地獄)

Promiseを使うとそれぞれの非同期処理を`then()`メソッドを使ってパイプのように繋げて書くことができ、ネストが深くなることなく書くことができます。

## 非同期処理のエラーハンドリングをまとめて書くことができる

コールバック関数だとそれぞれの非同期処理ごとにエラー処理を書く必要がありました。(それぞれの非同期処理でtry,catch構文を書く感じ)

ただ、連続で実施する非同期処理のいずれかでエラーになった場合には処理を終わらせたいなどの処理を書きたい場合も多いかと思います。
上記についてPromiseを使えば簡単に書くことができます。

実際に先ほど書いた例でもlistBuckets()もしくはlistObjects()のいずれかでエラーが発生した場合には`catch(function(error){})`が呼び出される仕組みとなっており、エラー処理をまとめて書いています。

具体的にエラーが発生したとは以下のいずれかを示します。

+ 非同期処理で例外(thrown new Error)が発生した
+ 非同期処理でreject()メソッドが呼び出された

AWS SDKの非同期処理を行う`AWS.request().promise()`でもエラー時には上記のいずれかが呼び出され、Errorオブジェクトが渡ることが確認できました。(非同期処理の第一引数のParamsに存在しないパラメータなど入れるとエラーが確認できます)

なお、今回の例の場合、listBuckets()メソッドでエラーになった場合、そのタイミングで`catch()`メソッドが呼びされるのでlistObjects()メソッドが呼びされることはありません。

## 並行した非同期処理が簡単に書ける

今回の例ではlistBuckets()メソッドでバケット名一覧を取得し後、各バケット名をキーにしてlistObjects()メソッドを呼んでいます。

もちろん、各バケット名をキーに順番にlistObjectを呼びこともできますが、せっかくNode.jsを使っているのであれば、非同期処理を並列で書くのがベターだと思います。

+ 同期処理の場合:listObjects(バケットA)が完了->listObjects(バケットB)が完了...->(バケット名Nまで)->任意の処理を実行
+ 非同期処理の場合:listObjects(バケットA),listObjects(バケットB),listObjects(バケットN)を並列に実行し、すべての処理が終わったら任意の処理を実行

上記を比較すると非同期処理を並列で実行できればその分、処理時間が短いので大きな利点と言えると思います。

ただし、非同期処理を並列で実行し、かつすべての処理が終わるまで待つというのは書くのが面倒そうです。Node.jsでは`async`などのモジュールを使って上記を実現することができましたが、Promiseを使えば簡単にできます。

具体的には`Promise.all()`メソッドを使います。

Promise.all()メソッドでは引数にPromiseオブジェクトの配列を渡すことで、渡されたPromiseの処理が全て終わったタイミングで`then()`メソッドが呼び出される仕組みとなります。今回の例でも各listObjects().promise()のオブジェクトの配列を渡し、全ての処理が終わった後に`then()`メソッドが呼びされるようになっています。

なお、非同期処理のいずれかが完了したタイミングで任意の処理を実行したい場合には`Promise.race()`メソッドを使います。