Node.js の Lambda 関数ハンドラーの定義 - AWS Lambda

Node.js の Lambda 関数ハンドラーの定義

Lambda 関数ハンドラーは、イベントを処理する関数コード内のメソッドです。関数が呼び出されると、Lambda はハンドラーメソッドを実行します。関数は、ハンドラーが応答を返すか、終了するか、タイムアウトするまで実行されます。

Node.js ハンドラーの基本

次の例の関数では、イベントオブジェクトの内容をログに記録して、そのログの場所を返します。

注記

このページは、CommonJS と ES モジュールハンドラーの両方の例を示します。これらの 2 つのハンドラータイプの違いについては、「ES モジュールとしての関数ハンドラーの指定」を参照してください。

ES module handler
export const handler = async (event, context) => { console.log("EVENT: \n" + JSON.stringify(event, null, 2)); return context.logStreamName; };
CommonJS module handler
exports.handler = async function (event, context) { console.log("EVENT: \n" + JSON.stringify(event, null, 2)); return context.logStreamName; };

関数を設定すると、ハンドラー設定の値はファイル名とエクスポートしたハンドラーメソッドの名前をドットで区切ったものになります。コンソールのデフォルトと、このガイドの例では、index.handler です。これは、 handler ファイルからエクスポートされた index.js メソッドを示します。

ランタイムでは、ハンドラーメソッドに引数を渡します。最初の引数は、呼び出し元からの情報を含む event オブジェクトです。呼び出し元は、Invoke を呼び出してこの情報を JSON 形式の文字列として渡し、ランタイムはそれをオブジェクトに変換します。AWS のサービスで関数を呼び出す場合、そのイベント構造はサービスによって異なります

2 番目の引数は、コンテキストオブジェクトです。この引数には、呼び出し、関数、および実行環境に関する情報が含まれます。前述の例では、関数は、コンテキストオブジェクトからログストリームの名前を取得し、それを呼び出し元に返します。

コールバック引数を使用することもできます。これは、非同期ではないハンドラーで呼び出してレスポンスを送信できる関数です。コールバックの代わりに async/await を使用することをお勧めします。async/await により、読みやすさ、エラー処理、および効率が向上します。async/await とコールバックの違いの詳細については、「コールバックの使用」を参照してください。

命名

関数を設定すると、ハンドラー設定の値はファイル名とエクスポートしたハンドラーメソッドの名前をドットで区切ったものになります。コンソールで作成された関数のデフォルトと、このガイドの例では、index.handler です。これは、index.js または index.mjs ファイルからエクスポートされた handler メソッドを示します。

異なるファイル名または関数ハンドラー名を使用してコンソールで関数を作成する場合は、デフォルトのハンドラー名を編集する必要があります。

関数ハンドラー名を変更するには (コンソール)
  1. Lambda コンソールの [関数] ページを開き、関数を選択します。

  2. [コード] タブを選択します。

  3. [ランタイム設定] ペインまでスクロールして、[編集] を選択します。

  4. [ハンドラー] には、関数ハンドラーの新しい名前を入力します。

  5. [Save] を選択します。

async/await の使用

コードが非同期タスクを実行する場合は、async/await パターンを使用して、ハンドラーが実行を確実に終了するようにします。async/await は、Node.js で非同期コードを記述するための簡潔で読みやすい方法であり、ネストされたコールバックや連鎖する promise を必要としません。async/await を使用すると、非同期かつノンブロッキングでありながら、同期コードのように読み取るコードを記述できます。

async キーワードは関数を非同期としてマークし、await キーワードは Promise が解決されるまで関数の実行を一時停止します。

注記

非同期イベントが完了するまでお待ちください。非同期イベントが完了する前に関数が戻る場合、関数が失敗したり、アプリケーションで予期しない動作が発生したりする可能性があります。これは、forEach ループに非同期イベントが含まれている場合に発生します。forEach ループは同期呼び出しを想定しています。詳細については、Mozilla ドキュメントの「Array.prototype.forEach()」を参照してください。

ES module handler
例 – async/await を使用した HTTP リクエスト
const url = "https://aws.amazon.com/"; export const handler = async(event) => { try { // fetch is available in Node.js 18 and later runtimes const res = await fetch(url); console.info("status", res.status); return res.status; } catch (e) { console.error(e); return 500; } };
CommonJS module handler
例 – async/await を使用した HTTP リクエスト
const https = require("https"); let url = "https://aws.amazon.com/"; exports.handler = async function (event) { let statusCode; await new Promise(function (resolve, reject) { https.get(url, (res) => { statusCode = res.statusCode; resolve(statusCode); }).on("error", (e) => { reject(Error(e)); }); }); console.log(statusCode); return statusCode; };

次の例では、async/await を使用して Amazon Simple Storage Service バケットを一覧表示します。

注記

この例を使用する前に、関数の実行ロールに Amazon S3 の読み取り許可があることを確認してください。

ES module handler
例 – async/await を使用した AWS SDK v3

この例では、nodejs18.x 以降のランタイムで利用できる「AWS SDK for JavaScript v3」を使用します。

import {S3Client, ListBucketsCommand} from '@aws-sdk/client-s3'; const s3 = new S3Client({region: 'us-east-1'}); export const handler = async(event) => { const data = await s3.send(new ListBucketsCommand({})); return data.Buckets; };
CommonJS module handler
例 – async/await を使用した AWS SDK v3

この例では、nodejs18.x 以降のランタイムで利用できる「AWS SDK for JavaScript v3」を使用します。

const { S3Client, ListBucketsCommand } = require('@aws-sdk/client-s3'); const s3 = new S3Client({ region: 'us-east-1' }); exports.handler = async (event) => { const data = await s3.send(new ListBucketsCommand({})); return data.Buckets; };

コールバックの使用

コールバックを使用する代わりに、async/await を使用して関数ハンドラーを宣言することをお勧めします。いくつかの理由により、async/await の方が適しています。

  • 読みやすさ: async/await コードは、コールバックコードよりも読みやすく理解しやすいです。コールバックコードは、すぐに理解するのが難しくなり、コールバック地獄に陥る可能性があります。

  • デバッグとエラー処理: コールバックベースのコードのデバッグは難しい場合があります。コールスタックを追跡するのが難しくなり、エラーが簡単に隠れてしまう可能性があります。async/await では、try/catch ブロックを使用してエラーを処理できます。

  • 効率: コールバックでは、多くの場合、コードのさまざまな部分を切り替える必要があります。async/await を使用すると、コンテキストスイッチの回数を減らすことができるため、コードがより効率的になります。

ハンドラーでコールバックを使用すると、関数は、イベントループが空になるか、関数がタイムアウトするまで実行を続けます。レスポンスは、すべてのイベントループタスクが完了するまで、呼び出し元に送信されません。関数がタイムアウトした場合は、エラーが返ります。すぐにレスポンスが返るようにランタイムを設定するには、context.callbackWaitsForEmptyEventLoop を false に設定します。

コールバック関数は、Error とレスポンスの 2 つの引数を取ります。応答オブジェクトは、JSON.stringify と互換性がある必要があります。

次の例の関数では、URL をチェックし、ステータスコードを呼び出し元に返します。

ES module handler
例 – callback を使用した HTTP リクエスト
import https from "https"; let url = "https://aws.amazon.com/"; export function handler(event, context, callback) { https.get(url, (res) => { callback(null, res.statusCode); }).on("error", (e) => { callback(Error(e)); }); }
CommonJS module handler
例 – callback を使用した HTTP リクエスト
const https = require("https"); let url = "https://aws.amazon.com/"; exports.handler = function (event, context, callback) { https.get(url, (res) => { callback(null, res.statusCode); }).on("error", (e) => { callback(Error(e)); }); };

次の例では、Amazon S3 のレスポンスは、有効になるとすぐに呼び出し元に返ります。イベントループで実行されているタイムアウトは停止し、次に関数が呼び出されたときに実行を継続します。

注記

この例を使用する前に、関数の実行ロールに Amazon S3 の読み取り許可があることを確認してください。

ES module handler
例 – callbackWaitsForEmptyEventLoop を使用した AWS SDK v3

この例では、nodejs18.x 以降のランタイムで利用できる「AWS SDK for JavaScript v3」を使用します。

import AWS from "@aws-sdk/client-s3"; const s3 = new AWS.S3({}); export const handler = function (event, context, callback) { context.callbackWaitsForEmptyEventLoop = false; s3.listBuckets({}, callback); setTimeout(function () { console.log("Timeout complete."); }, 5000); };
CommonJS module handler
例 – callbackWaitsForEmptyEventLoop を使用した AWS SDK v3

この例では、nodejs18.x 以降のランタイムで利用できる「AWS SDK for JavaScript v3」を使用します。

const AWS = require("@aws-sdk/client-s3"); const s3 = new AWS.S3({}); exports.handler = function (event, context, callback) { context.callbackWaitsForEmptyEventLoop = false; s3.listBuckets({}, callback); setTimeout(function () { console.log("Timeout complete."); }, 5000); };

Node.js Lambda 関数のコードのベストプラクティス

Lambda 関数をビルドするときは、次のリストのガイドラインに従って、コーディングのベストプラクティスを実行してください。

  • Lambda ハンドラーをコアロジックから分離します。これにより、関数の単体テストが実行しやすくなります。Node.js では、次のようになります。

    exports.myHandler = function(event, context, callback) { var foo = event.foo; var bar = event.bar; var result = MyLambdaFunction (foo, bar); callback(null, result); } function MyLambdaFunction (foo, bar) { // MyLambdaFunction logic here }
  • 関数のデプロイパッケージ内で依存関係を制御します。AWS Lambda 実行環境には多数のライブラリが含まれています。Node.js および Python ランタイムの場合、ライブラリには AWS SDK が含まれます。最新の機能やセキュリティ更新プログラムを有効にするために、Lambda はこれらのライブラリを定期的に更新します。この更新により、Lambda 関数の動作が微妙に変化する場合があります。関数で使用する依存関係を完全に制御するには、すべての依存関係をデプロイパッケージでパッケージングします。

  • 依存関係の複雑さを最小限に抑えます。フレームワークを単純化して、実行環境起動時のロードを高速化します。

  • デプロイパッケージをランタイムに必要な最小限のサイズにします。これにより、呼び出しに先立ってデプロイパッケージをダウンロードして解凍する所要時間が短縮されます。

  • 実行環境の再利用を活用して関数のパフォーマンスを向上させます。関数ハンドラー外で SDK クライアントとデータベース接続を初期化し、静的なアセットを /tmp ディレクトリにローカルにキャッシュします。関数の同じインスタンスで処理された後続の呼び出しは、これらのリソースを再利用できます。これにより、関数の実行時間が短縮され、コストが節約されます。

    呼び出し間でデータが漏れるのを防ぐため、実行環境を使用してセキュリティ上の懸念があるユーザーデータ、イベント、またはその他の情報を保存しないでください。関数がハンドラー内のメモリに保存できない変更可能な状態に依存している場合は、ユーザーごとに個別の関数または個別のバージョンの関数を作成することを検討してください。

  • keep-alive ディレクティブを使用して永続的な接続を維持します。Lambda は、時間の経過とともにアイドル状態の接続を消去します。関数を呼び出すときにアイドル状態の接続を再利用しようとすると、接続エラーが発生します。永続的な接続を維持するには、ランタイムに関連付けられている keep-alive ディレクティブを使用します。例については、「Node.js で Keep-alive を使用して接続を再利用する」を参照してください。

  • 環境変数を使用して、オペレーショナルパラメータを関数に渡します。たとえば、Amazon S3 バケットに書き込む場合、書き込み先のバケット名はハードコーディングせずに、環境変数として設定します。

  • Lambda 関数内で任意の条件が満たされるまで、その関数自身を自動的に呼び出すような再帰的なコードを使用しないでください。これを行うと意図しないボリュームで関数が呼び出され、料金が急増する可能性があります。誤ってこのようなコードを使用した場合は、すぐに関数の予約済同時実行数を 0 に設定して、コードを更新している間のすべての関数の呼び出しをスロットリングします。

  • Lambda 関数コードで文書化されていない非公開の API を使用しないでください。AWS Lambda マネージドランタイムでは、Lambda が Lambda の内部 API にセキュリティと機能面の更新を定期的に適用します。これらの内部 API 更新には後方互換性がないことがあり、関数にこれらの非公開 API に対する依存関係がある場合、呼び出しの失敗などの意図しない結果につながります。公開されている API のリストについては、「API リファレンス」を参照してください。

  • 冪等性コードを記述します。関数の記述に冪等性コードを使用すると、重複するイベントが同じ方法で処理されるようになります。コードでは、イベントを適切に検証し、重複するイベントを適切に処理する必要があります。詳細については、「Lambda 関数を冪等にするにはどうすればよいですか?」を参照してください。