- 公開日
- 最終更新日
【Lambda】awaitがなくてもPromise解決を待つ
この記事を共有する
はじめに
パーソルサーバーワークスの嶋田です。
先日、SAMを使っていたときに、
ひょんなことから lambdaHandler の async を消しました。
その際にAWSドキュメントで明言されていない振る舞いを発見したので、簡単に紹介します。
結論は、本ブログのタイトルにある通りです。
前提
- sam init で以下の通りセットアップが完了していること
- Which template source would you like to use?
- 1 - AWS Quick Start Templates
- Choose an AWS Quick Start application template
- 1 - Hello World Example
- Use the most popular runtime and package type? (python3.13 and zip) [y/N]: N
- Which runtime would you like to use?
- 11 - nodejs22.x
- What package type would you like to use?
- 1 - Zip
- Select your starter template
- 1 - Hello World Example
- Which template source would you like to use?
記載の無い箇所はEnter連打です。最後に設定するProject Nameはお好み。
コードの準備
以下のLambdaコード及びSAMテンプレートの準備をします。
Lambdaコード
// app.mjs
export const lambdaHandler = (event, context) => {
let response = 'hello world'
console.log('before response', response)
response = waitSetTimeout()
console.log('after response', response)
return response;
};
function waitSetTimeout() {
return new Promise(resolve => {
setTimeout(() => {
let response = 'hello setTimeout'
console.log('waitSetTimeout/setTimeout')
resolve(response)
}, 3000);
})
}
SAMテンプレート
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Sample SAM Template for sam-app
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs22.x
Timeout: 300
Architectures:
- x86_64
FunctionUrlConfig:
AuthType: NONE
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Outputs:
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunctionUrl:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunctionUrl.FunctionUrl
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
まずはLambdaを動かしてみる
LambdaコードとSAMテンプレートの準備が整ったら、SAMプロジェクトにて
$ sam build && sam deploy --no-confirm-changeset を実行します。
デプロイ完了時に Outputs として出力される、
HelloWorldFunctionUrl の Value URLが、以下のように出力されます。
Key HelloWorldFunctionUrl Description Hello World Lambda Function ARN Value https://ll2rbx576fg5ymziwxrt6r6tkm0vfapv.lambda-url.ap-northeast-1.on.aws/
あとで curl を実行したいので、URLをメモします。
Lambdaを実行する前に、値確認用に仕込んだconsole.logもチェックしたいので、
$ sam logs --tail --start-time "0 minutes ago" を、別のシェルで実行します。
挙動を観測する準備が整ったので curl を実行します。
curl を実行したシェルには hello setTimeout が表示されます。
$ curl https://ll2rbx576fg5ymziwxrt6r6tkm0vfapv.lambda-url.ap-northeast-1.on.aws/ hello setTimeout
sam logs を実行しているシェルも確認してみます。
$ sam logs --tail --start-time "0 minutes ago"
2025/03/09/[$LATEST]f52d36e198a640359ecb25fc1bf6b024 2025-03-09T03:43:06.721000 INIT_START Runtime Version: nodejs:22.v29 Runtime Version ARN: arn:aws:lambda:ap-northeast-1::runtime:f494bf5385768c1a5f722eae90b6dd3d343c96ba7ec22b34f5c819e3e8511722
2025/03/09/[$LATEST]f52d36e198a640359ecb25fc1bf6b024 2025-03-09T03:43:06.875000 START RequestId: ebbecc7b-fe6d-4aca-a530-f2df910cfabf Version: $LATEST
2025/03/09/[$LATEST]f52d36e198a640359ecb25fc1bf6b024 2025-03-09T03:43:06.877000 2025-03-09T03:43:06.877Z ebbecc7b-fe6d-4aca-a530-f2df910cfabf INFO before response hello world
2025/03/09/[$LATEST]f52d36e198a640359ecb25fc1bf6b024 2025-03-09T03:43:06.884000 2025-03-09T03:43:06.884Z ebbecc7b-fe6d-4aca-a530-f2df910cfabf INFO after response Promise { <pending> }
2025/03/09/[$LATEST]f52d36e198a640359ecb25fc1bf6b024 2025-03-09T03:43:09.888000 2025-03-09T03:43:09.888Z ebbecc7b-fe6d-4aca-a530-f2df910cfabf INFO waitSetTimeout/setTimeout
2025/03/09/[$LATEST]f52d36e198a640359ecb25fc1bf6b024 2025-03-09T03:43:09.891000 END RequestId: ebbecc7b-fe6d-4aca-a530-f2df910cfabf
2025/03/09/[$LATEST]f52d36e198a640359ecb25fc1bf6b024 2025-03-09T03:43:09.891000 REPORT RequestId: ebbecc7b-fe6d-4aca-a530-f2df910cfabf Duration: 3014.92 ms Billed Duration: 3015 ms Memory Size: 128 MB Max Memory Used: 70 MB Init Duration: 150.93 ms
START ~ END の間に INFO が表示されていることからも、
Lambda関数の処理はawaitなしでも全てのコードを処理してから、returnしていそうです。
ローカルで同等のコードを実行し、結果を比較する
Lambdaの挙動に違和感のあった私は、
ローカルでPromiseのコードを書いて動作を確認しました。
// local-app.mjs
main()
function main() {
console.log('response', lambdaHandler())
}
function lambdaHandler() {
let response = 'hello world'
console.log('before response', response)
response = waitSetTimeout()
console.log('after response', response)
return response;
}
function waitSetTimeout() {
return new Promise(resolve => {
setTimeout(() => {
let response = 'hello setTimeout'
console.log('waitSetTimeout/setTimeout')
resolve(response)
}, 3000);
})
}
上記は、Lambdaハンドラーで実行していた処理を、
ローカルに local-app.mjs ファイルを新規作成し、模倣したものです。
試しに、ローカルでもコードを実行してみます。
$ node local-app.mjs
before response hello world
after response Promise { <pending> }
response Promise { <pending> }
waitSetTimeout/setTimeout
以上のことから、Lambdaとローカルで、Promiseの挙動が異なるように見受けられます。
async / await ちゃんとつける
app.mjs を編集して、 async / await を記述した場合の挙動も見ておきます。 動作確認の流れはかわらないため、ダイジェスト的に手順を記載します。
Lambdaコードの書き換え
// app.mjs
export const lambdaHandler = async (event, context) => {
let response = 'hello world'
console.log('before response', response)
response = await waitSetTimeout()
console.log('after response', response)
return response;
};
function waitSetTimeout() {
return new Promise(resolve => {
setTimeout(() => {
let response = 'hello setTimeout'
console.log('waitSetTimeout/setTimeout')
resolve(response)
}, 3000);
})
}
変更後のLambdaコードの実行
$ curl https://ll2rbx576fg5ymziwxrt6r6tkm0vfapv.lambda-url.ap-northeast-1.on.aws/ hello setTimeout
$ sam logs --tail --start-time "0 minutes ago" 2025/03/09/[$LATEST]6d56e77bf7dc40c2b5676999d77b5378 2025-03-09T03:54:33.858000 START RequestId: 8bacc817-3d19-487e-923b-8ac350094525 Version: $LATEST 2025/03/09/[$LATEST]6d56e77bf7dc40c2b5676999d77b5378 2025-03-09T03:54:33.859000 2025-03-09T03:54:33.859Z 8bacc817-3d19-487e-923b-8ac350094525 INFO before response hello world 2025/03/09/[$LATEST]6d56e77bf7dc40c2b5676999d77b5378 2025-03-09T03:54:36.862000 2025-03-09T03:54:36.862Z 8bacc817-3d19-487e-923b-8ac350094525 INFO waitSetTimeout/setTimeout 2025/03/09/[$LATEST]6d56e77bf7dc40c2b5676999d77b5378 2025-03-09T03:54:36.863000 2025-03-09T03:54:36.863Z 8bacc817-3d19-487e-923b-8ac350094525 INFO after response hello setTimeout 2025/03/09/[$LATEST]6d56e77bf7dc40c2b5676999d77b5378 2025-03-09T03:54:36.865000 END RequestId: 8bacc817-3d19-487e-923b-8ac350094525 2025/03/09/[$LATEST]6d56e77bf7dc40c2b5676999d77b5378 2025-03-09T03:54:36.865000 REPORT RequestId: 8bacc817-3d19-487e-923b-8ac350094525 Duration: 3005.65 ms Billed Duration: 3006 ms Memory Size: 128 MB Max Memory Used: 70 MB
before -> waitSetTimeout -> after の順に実行されていて、想定通りです
ローカルでの模倣コード書き換え
// local-app.mjs
main()
async function main() {
console.log('response', await lambdaHandler())
}
async function lambdaHandler() {
let response = 'hello world'
console.log('before response', response)
response = await waitSetTimeout()
console.log('after response', response)
return response;
}
function waitSetTimeout() {
return new Promise(resolve => {
setTimeout(() => {
let response = 'hello setTimeout'
console.log('waitSetTimeout/setTimeout')
resolve(response)
}, 3000);
})
}
ローカルでの模倣コードの実行
$ node local-app.mjs before response hello world waitSetTimeout/setTimeout after response hello setTimeout response hello setTimeout
before -> waitSetTimeout -> after の順に実行されていて、想定通りです
公式に問い合わせ
Lambdaがawait無しでawaitの振る舞いをする件について公式に問い合わせたところ、
以下の通り回答をいただきました。
本動作に関しまして、公開しているドキュメントなどに明示的に記載されているものは見つかりませんでした
非同期タスクを実行する場合は、async/awaitパターンのご使用を推奨し、非同期イベントが完了するまでお待ちいただくようご依頼しております。
とのことでした。
確かに、公式ドキュメントには推奨する旨記載があるので、推奨される使い方ではないです。
サポートの方の対応が非常に丁寧で、以下のフォローコメントを頂きました。
もし、上記以上の情報が必要な場合、当該動作に関しまして具体的な懸念点やユースケース、
async/awaitパターンの宣言が難しい理由などの詳細をご共有いただけますでしょうか。
ご共有いただいた情報を元に追加でお客様にご案内できる情報が無いか確認いたしたく存じます。
なお、追加の調査の結果、Lambdaの内部仕様に関することなど、お客様へのご案内が難しい可能性
がございますこと、あらかじめご理解ご了承いただけますと幸いです。
最後に
その他に、Promise.allでasync function配列を実行してみるなど確認をしてみましたが、
このあたりの挙動に影響は与えないようでした。(Promise.all内で並列実行される)
教訓
- 公式的に推奨されるアプローチがあれば、そのレールに則ろう
- 公式ドキュメントちゃんと読もう
以上
お疲れさまでした。
この記事は私が書きました
嶋田 龍登
記事一覧インフラからアプリのことまでなんでもやりたい、フルスタックを目指すエンジニア