Lambdaで値を返す方法まとめ

# 結論

handlerをasyncにして普通にreturnで99.9%くらいOK

## async + return

最も普通な方法です.

```js
exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};
```

### async + returnでsetTimeoutする

```js
exports.handler = async (event) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    
    setTimeout(() => {
        console.log("1000ms後");
    }, 1000);
    
    setTimeout(() => {
        console.log("2000ms後");
    }, 2000);
    
    return response;
};
```

のような場合, 1回目の実行でreturnするまでが1000ms未満の場合, console.logには出力されず, ウォームスタートした場合に次回以降出力されます.

1回目

```text
START RequestId: 4978d8d6-0550-46d2-ad36-769858d22fa3 Version: $LATEST
END RequestId: 4978d8d6-0550-46d2-ad36-769858d22fa3
REPORT RequestId: 4978d8d6-0550-46d2-ad36-769858d22fa3	Duration: 2.50 ms	Billed Duration: 3 ms	Memory Size: 128 MB	Max Memory Used: 85 MB	Init Duration: 391.34 ms
```

2回目 (2秒以上開ける)

```text
START RequestId: e4dcdbb1-a150-43b4-9e80-c7a776837c16 Version: $LATEST
2021-09-01T03:17:49.058Z	4978d8d6-0550-46d2-ad36-769858d22fa3	INFO	1000ms後
2021-09-01T03:17:49.059Z	4978d8d6-0550-46d2-ad36-769858d22fa3	INFO	2000ms後
END RequestId: e4dcdbb1-a150-43b4-9e80-c7a776837c16
REPORT RequestId: e4dcdbb1-a150-43b4-9e80-c7a776837c16	Duration: 160.53 ms	Billed Duration: 161 ms	Memory Size: 128 MB	Max Memory Used: 85 MB
```

console.logで出力される際についてくる実行IDが, 前回のものになっていることがわかります. このsetTimeout内の`console.log`は, Lambda実行後かつhandler実行前に実行されます. 
(`setTimeout(() => {console.log(new Date());}, 1000);` してみましょう)

3回目実行直後に4回目

```text
START RequestId: a87c55c4-73b6-402b-9b51-924d7ca7e178 Version: $LATEST
END RequestId: a87c55c4-73b6-402b-9b51-924d7ca7e178
REPORT RequestId: a87c55c4-73b6-402b-9b51-924d7ca7e178	Duration: 1.14 ms	Billed Duration: 2 ms	Memory Size: 128 MB	Max Memory Used: 86 MB
```

ウォームスタートしていますが, 前回の実行のsetTimeoutが完了していないため, 何も出力されません.

5回目(2秒以上待機)

```text
START RequestId: 20217709-e518-48fe-ad60-0fe1c7dd627e Version: $LATEST
2021-09-01T03:19:13.790Z	a87c55c4-73b6-402b-9b51-924d7ca7e178	INFO	1000ms後
2021-09-01T03:19:13.790Z	a87c55c4-73b6-402b-9b51-924d7ca7e178	INFO	1000ms後
2021-09-01T03:19:13.790Z	a87c55c4-73b6-402b-9b51-924d7ca7e178	INFO	2000ms後
2021-09-01T03:19:13.790Z	a87c55c4-73b6-402b-9b51-924d7ca7e178	INFO	2000ms後
END RequestId: 20217709-e518-48fe-ad60-0fe1c7dd627e
REPORT RequestId: 20217709-e518-48fe-ad60-0fe1c7dd627e	Duration: 1.47 ms	Billed Duration: 2 ms	Memory Size: 128 MB	Max Memory Used: 86 MB
```

溜まっていたsetTimeoutが処理された後にLambdaを実行したため, 一気に表示されました.

詳しくはLambdaのライフサイクルで


## 非async + Promise

asyncで普通にreturnすれば値を返せるので, 当然Promiseを返すとうまくいきます.

```js
exports.handler = (event) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return new Promise((resolve) => {
        resolve(response)
    });
};
```

非asyncな場合, 普通にreturnするだけだとnullになります.

## callback

handlerの第3引数のcallbackで返す方法です. asyncかは問いません.

```js
exports.handler = async (event, context, callback) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    
    callback(null, response);
    
    // callback後も実行される
    console.log("callback後");
    setTimeout(() => {
        response.statusCode = 400; // ここの実行後にcallbackに入れた値が返るため, 400が出力される
    }, 1000);
};
```

この場合, setTimeoutなどの非同期処理が全て終わってからcallbackに入れた値が返されます.

### 複数回callbackした場合

最も最初のcallbackのみ利用されます.

```js
exports.handler = async (event, context, callback) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };

    setTimeout(() => {
        callback(null, response);
    }, 1000);
    callback(null, "hoge");
    callback(null, "fuga");
};
```

```text
hoge
```

### エラーの場合は第1引数

```js
callback(new Error("era-"));
```

## context

`context.done`, `context.succeed`, `context.fail`が使えます.

```js
exports.handler = async (event, context, callback) => {
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };

    context.succeed(response);

    setTimeout(() => {
        console.log("fuga"); // この時点では実行されない (次回実行時にconsole.logされている)
    }, 1000);
    console.log("hogehoge"); // 実行される
};
```

つまり, 非同期は後回しになるものの, `succeed`以降も処理が続く, returnとcallbackの中間の性質を持っています. ぶっちゃけ使いません. async + returnでいいです.

## callback, context, return全部並べてみる

普通に最初のものが処理されます.

```js
exports.handler = async (event, context, callback) => {
    context.succeed("hoge");
    callback(null, "fuga");

    setTimeout(() => {
        console.log("a"); // 次回までお預け
    }, 1000);
    return "hige";
};
```

```text
hoge
```

```js
exports.handler = async (event, context, callback) => {
    callback(null, "fuga");
    context.succeed("hoge");
    
    setTimeout(() => {
        console.log("a"); // 実行される
    }, 1000);
    return "hige";
};
```

```text
fuga
```

どのみちこんな書き方したらコードレビューで凄まじい数のchange requestがやってくるはずなので, 普通書かないです.

## さいごに

他にも面白いのあったら教えてください🙏