

# 定义采用 Node.js 的 Lambda 函数处理程序
<a name="nodejs-handler"></a>

Lambda 函数*处理程序*是函数代码中处理事件的方法。当调用函数时，Lambda 运行处理程序方法。您的函数会一直运行，直到处理程序返回响应、退出或超时。

本页介绍了如何使用 Node.js Lambda 函数处理程序，包括项目设置选项、命名约定和最佳实践。本页还包括 Node.js Lambda 函数的示例，在示例中该函数接收订单信息，生成文本文件收据，然后将此文件放入 Amazon Simple Storage Service（Amazon S3）存储桶中。有关如何在编写函数后部署函数的信息，请参阅[使用 .zip 文件归档部署 Node.js Lambda 函数](nodejs-package.md)或[使用容器映像部署 Node.js Lambda 函数](nodejs-image.md)。

**Topics**
+ [设置 Node.js 处理程序项目](#nodejs-handler-setup)
+ [示例 Node.js Lambda 函数代码](#nodejs-example-code)
+ [CommonJS 和 ES 模块](#nodejs-commonjs-es-modules)
+ [Node.js 初始化](#nodejs-initialization)
+ [处理程序命名约定](#nodejs-handler-naming)
+ [定义和访问输入事件对象](#nodejs-example-input)
+ [Node.js 函数的有效处理程序模式](#nodejs-handler-signatures)
+ [在处理程序中使用适用于 JavaScript v3 的 SDK](#nodejs-example-sdk-usage)
+ [评估环境变量](#nodejs-example-envvars)
+ [使用全局状态](#nodejs-handler-state)
+ [Node.js Lambda 函数的代码最佳实践](#nodejs-best-practices)

## 设置 Node.js 处理程序项目
<a name="nodejs-handler-setup"></a>

有多种方法可初始化 Node.js Lambda 项目。例如，您可以使用 `npm` 创建标准 Node.js 项目、创建 [AWS SAM 应用程序](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-init.html#using-sam-cli-init-new)或创建 [AWS CDK 应用程序](lambda-cdk-tutorial.md#lambda-cdk-step-1)。

使用 `npm` 创建项目：

```
npm init
```

此命令将初始化项目，并生成管理项目元数据和依赖项的 `package.json` 文件。

您的函数代码位于 `.js` 或 `.mjs` JavaScript 文件中。在以下示例中，我们将此文件命名为 `index.mjs`，因为其使用了 ES 模块处理程序。Lambda 同时支持 ES 模块和 CommonJS 处理程序。有关更多信息，请参阅 [CommonJS 和 ES 模块](#nodejs-commonjs-es-modules)。

典型的 Node.js Lambda 函数项目遵循以下一般结构：

```
/project-root
  ├── index.mjs — Contains main handler
  ├── package.json — Project metadata and dependencies
  ├── package-lock.json — Dependency lock file
  └── node_modules/ — Installed dependencies
```

## 示例 Node.js Lambda 函数代码
<a name="nodejs-example-code"></a>

以下示例 Lambda 函数代码接收有关订单的信息，生成文本文件接收，并将此文件放入 Amazon S3 存储桶中。

**Example index.mjs Lambda 函数**  

```
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

// Initialize the S3 client outside the handler for reuse
const s3Client = new S3Client();

/**
 * Lambda handler for processing orders and storing receipts in S3.
 * @param {Object} event - Input event containing order details
 * @param {string} event.order_id - The unique identifier for the order
 * @param {number} event.amount - The order amount
 * @param {string} event.item - The item purchased
 * @returns {Promise<string>} Success message
 */
export const handler = async(event) => {
    try {
        // Access environment variables
        const bucketName = process.env.RECEIPT_BUCKET;
        if (!bucketName) {
            throw new Error('RECEIPT_BUCKET environment variable is not set');
        }

        // Create the receipt content and key destination
        const receiptContent = `OrderID: ${event.order_id}\nAmount: $${event.amount.toFixed(2)}\nItem: ${event.item}`;
        const key = `receipts/${event.order_id}.txt`;

        // Upload the receipt to S3
        await uploadReceiptToS3(bucketName, key, receiptContent);

        console.log(`Successfully processed order ${event.order_id} and stored receipt in S3 bucket ${bucketName}`);
        return 'Success';
    } catch (error) {
        console.error(`Failed to process order: ${error.message}`);
        throw error;
    }
};

/**
 * Helper function to upload receipt to S3
 * @param {string} bucketName - The S3 bucket name
 * @param {string} key - The S3 object key
 * @param {string} receiptContent - The content to upload
 * @returns {Promise<void>}
 */
async function uploadReceiptToS3(bucketName, key, receiptContent) {
    try {
        const command = new PutObjectCommand({
            Bucket: bucketName,
            Key: key,
            Body: receiptContent
        });

        await s3Client.send(command);
    } catch (error) {
        throw new Error(`Failed to upload receipt to S3: ${error.message}`);
    }
}
```

此 `index.mjs` 文件包含以下代码部分：
+ `import` 数据块：使用此数据块来包含 Lambda 函数所需的库，例如 [AWS SDK 客户端](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/the-request-object.html)。
+ `const s3Client` 声明：用于在处理程序函数之外初始化 [Amazon S3 客户端](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/)。这会导致 Lambda 在[初始化阶段](lambda-runtime-environment.md#runtimes-lifecycle-ib)运行此代码，并保留客户端以供[多次调用时重复使用](lambda-runtime-environment.md#execution-environment-reuse)。
+ JSDoc 注释块：使用 [JSDoc 注释](https://jsdoc.app/about-getting-started)为处理程序定义输入和输出类型。
+ `export const handler`：这是 Lambda 调用的主要处理程序函数。部署函数时，请为[处理程序](https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html#lambda-CreateFunction-request-Handler)属性指定 `index.handler`。`Handler` 属性的值是文件的名称和导出的处理程序方法的名称（由点分隔）。
+ `uploadReceiptToS3` 函数：这是主要处理程序函数引用的帮助函数。

要使此函数正常运行，其[执行角色](lambda-intro-execution-role.md)必须允许 `s3:PutObject` 操作。此外，请确保您定义了 `RECEIPT_BUCKET` 环境变量。成功调用后，Amazon S3 存储桶应包含接收文件。

## CommonJS 和 ES 模块
<a name="nodejs-commonjs-es-modules"></a>

Node.js 支持两个模块系统：CommonJS 和 ECMAScript 模块（ES 模块）。Lambda 建议使用 ES 模块，因为它支持顶层的 await 语句，这使得异步任务能够在[执行环境初始化](#nodejs-initialization)期间完成。

Node.js 将带有 `.cjs` 文件扩展名的文件视为 CommonJS 模块，而 `.mjs` 扩展程序则表示 ES 模块。默认情况下，Node.js 将带有 `.js` 文件扩展名的文件视为 CommonJS 模块。通过在函数的 `package.json` 文件中将 `type` 指定为 `module`，您可以将 Node.js 配置为将 `.js` 文件视为 ES 模块。您可以在 Lambda 中配置 Node.js，使其能够通过在 `NODE_OPTIONS` 环境变量中添加 `—experimental-detect-module` 标识来自动检测一个 `.js` 文件应被视为 CommonJS 还是 ES 模块格式。有关更多信息，请参阅[实验性 Node.js 特征](lambda-nodejs.md#nodejs-experimental-features)。

以下示例显示了使用 ES 模块和 CommonJS 模块编写的函数处理程序。本页上的其余示例均使用 ES 模块。

------
#### [ ES module example ]

**Example – ES 模块处理程序**  

```
const url = "https://aws.amazon.com/";

export const handler = async(event) => {
    try {
        const res = await fetch(url);
        console.info("status", res.status);
        return res.status;
    }
    catch (e) {
        console.error(e);
        return 500;
    }
};
```

------
#### [ CommonJS module example ]

**Example – CommonJS 模块处理程序**  

```
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;
};
```

------

## Node.js 初始化
<a name="nodejs-initialization"></a>

Node.js 使用一种非阻止式 I/O 模型，该模型通过事件循环支持高效的异步操作。例如，如果 Node.js 进行网络调用，则该函数将继续处理其他操作，而不会因等待网络响应而阻塞。当收到网络响应后，会将其放入回调队列中。当前任务完成时，便会处理队列中的任务。

Lambda 建议使用顶层的 await 语句，以使执行环境初始化期间启动的异步任务在初始化期间完成。初始化期间未完成的异步任务通常会在第一次函数调用期间运行。这可能会导致意外行为或错误。例如，您的函数初始化可能会进行网络调用，以从 AWS Parameter Store 中获取参数。如果在初始化过程中未完成此任务，则调用时该值可能会为 null。初始化和调用之间也可能存在延迟，这可能会在时间敏感的操作中触发错误。特别是，AWS 服务调用可以依赖于时间敏感的请求签名，如果在初始化阶段未完成调用，则会导致服务调用失败。在初始化阶段完成任务通常会提升冷启动性能，而使用预置并发时，首次调用的性能也会有所提升。有关更多信息，请参阅博客文章[在 AWS Lambda 中使用 Node.js ES 模块和顶层 await 语句](https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda)。

## 处理程序命名约定
<a name="nodejs-handler-naming"></a>

配置函数时，[处理程序](https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html#lambda-CreateFunction-request-Handler)设置的值是文件的名称和导出的处理程序方法的名称（由点分隔）。控制台中创建的函数的默认值为 `index.handler`，这也是本指南所用示例的值。这表示从 `index.js` 或 `index.mjs` 文件中导出的 `handler` 方法。

如果您在控制台中使用不同的文件名或函数处理程序名称创建函数，则必须编辑默认处理程序名称。

**更改函数处理程序名称（控制台）**

1. 打开 Lambda 控制台的[函数](https://console.aws.amazon.com/lambda/home#/functions)页面，然后选择一个函数。

1. 选择**节点**选项卡。

1. 向下滚动到**运行时设置**窗格并选择**编辑**。

1. 在**处理程序**中，输入函数处理程序的新名称。

1. 选择**保存**。

## 定义和访问输入事件对象
<a name="nodejs-example-input"></a>

JSON 是 Lambda 函数最常用且最标准的输入格式。在此示例中，该函数需要类似于下方的输入：

```
{
    "order_id": "12345",
    "amount": 199.99,
    "item": "Wireless Headphones"
}
```

在使用 Node.js Lambda 函数时，您可以使用 JSDoc 注释定义输入事件的预期形状。在此示例中，我们在处理程序的 JSDoc 注释中定义了输入结构：

```
/**
 * Lambda handler for processing orders and storing receipts in S3.
 * @param {Object} event - Input event containing order details
 * @param {string} event.order_id - The unique identifier for the order
 * @param {number} event.amount - The order amount
 * @param {string} event.item - The item purchased
 * @returns {Promise<string>} Success message
 */
```

在 JSDoc 注释中定义了这些类型后，您可以直接在代码中访问事件对象的字段。例如，`event.order_id` 从原始输入中检索 `order_id` 的值。

## Node.js 函数的有效处理程序模式
<a name="nodejs-handler-signatures"></a>

建议您使用 [async/await](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/using-async-await.html) 来声明函数处理程序，而不是使用[回调](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/using-a-callback-function.html)。Async/await 是一种简洁、易读的异步代码编写方式，无需使用嵌套回调或链式承诺。使用 Async/await 时，您编写的代码看起来与同步代码类似，同时仍然是异步和非阻止式的。

### 异步函数处理程序（推荐）
<a name="nodejs-handler-async"></a>

`async` 关键字会将函数标记为异步，`await` 关键字会暂停函数的执行，直到 `Promise` 完成解析为止。处理程序接受以下参数：
+ `event`：包含传递给您函数的输入数据。
+ `context`：包含有关调用、函数和执行环境的信息。有关更多信息，请参阅 [使用 Lambda 上下文对象检索 Node.js 函数信息](nodejs-context.md)。

以下为 async/await 模式的有效签名：

```
export const handler = async (event) => { };
```

```
export const handler = async (event, context) => { };
```

### 同步函数处理程序
<a name="nodejs-handler-synchronous"></a>

如果您的函数不执行任何异步任务，则可以使用同步函数处理程序，并采用以下函数签名之一：

```
export const handler = (event) => { };
```

```
export const handler = (event, context) => { };
```

### 响应流式处理函数处理程序
<a name="nodejs-handler-response-streaming"></a>

Lambda 支持使用 Node.js 进行响应流式处理。响应流式处理函数处理程序使用 `awslambda.streamifyResponse()` 修饰器并采用 3 个参数：`event`、`responseStream` 和 `context`。函数签名是：

```
export const handler = awslambda.streamifyResponse(async (event, responseStream, context) => { });
```

有关更多信息，请参阅 [Lambda 函数的响应流](configuration-response-streaming.md)。

### 基于回调的函数处理程序
<a name="nodejs-handler-callback"></a>

**注意**  
基于回调的函数处理程序仅在 Node.js 22 及以下版本中才被支持。从 Node.js 24 开始，应使用异步函数处理程序实施异步任务。

基于回调的函数处理程序必须使用事件、上下文和回调参数。示例：

```
export const handler = (event, context, callback) => { };
```

回调函数需要一个 `Error` 和一个响应，该响应必须是 JSON 可序列化的。函数会一直执行，直到[事件循环](https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/)为空或函数超时为止。在完成所有事件循环任务之前，不会将响应发送给调用方。如果函数超时，则会返回 error。可以通过将 [context.callbackWaitsForEmptyEventLoop](nodejs-context.md) 设置为 false，从而将运行时配置为立即发送响应。

**Example – 包含 callback 的 HTTP 请求**  
以下示例函数检查 URL 并向调用方返回状态代码。  

```
import https from "https";
let url = "https://aws.amazon.com/";

export const handler = (event, context, callback) => {
  https.get(url, (res) => {
    callback(null, res.statusCode);
  }).on("error", (e) => {
    callback(Error(e));
  });
};
```

## 在处理程序中使用适用于 JavaScript v3 的 SDK
<a name="nodejs-example-sdk-usage"></a>

通常，您将使用 Lambda 函数与其他 AWS 资源进行交互或对其进行更新。与此类资源最简单的交互方法是使用 适用于 JavaScript 的 AWS SDK。所有[支持的 Lambda Node.js 运行时](lambda-nodejs.md#nodejs-supported-runtimes)都包含[适用于 JavaScript 版本 3 的 SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/)。但是，强烈建议您在部署包中包含所需的 AWS SDK 客户端。这样可以最大限度地提高未来 Lambda 运行时更新期间的[向后兼容性](runtimes-update.md#runtime-update-compatibility)。只有在无法包含其他程序包时（例如，在 AWS CloudFormation 模板中使用 Lambda 控制台代码编辑器或内联代码时），才依赖运行时提供的 SDK。

要向函数添加 SDK 依赖项，请使用适用于所需特定 SDK 客户端的 `npm install` 命令。在示例代码中，我们使用了 [Amazon S3 客户端](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/)。在包含 `package.json` 文件的目录中，运行以下命令添加此依赖项：

```
npm install @aws-sdk/client-s3
```

在函数代码中，导入所需的客户端和命令，如示例函数所示：

```
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
```

然后，初始化 [Amazon S3 客户端](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/)：

```
const s3Client = new S3Client();
```

在此示例中，我们在主处理程序函数之外初始化了 Amazon S3 客户端，以免每次调用函数时都必须对其进行初始化。初始化 SDK 客户端后，就可以使用它为 AWS 服务发出 API 调用。示例代码按如下方式调用 Amazon S3 [PutObject](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/command/PutObjectCommand/) API：

```
const command = new PutObjectCommand({
    Bucket: bucketName,
    Key: key,
    Body: receiptContent
});
```

## 评估环境变量
<a name="nodejs-example-envvars"></a>

在处理程序代码中，您可以使用 `process.env` 引用任意[环境变量](configuration-envvars.md)。在此示例中，我们使用以下代码行来引用已定义的 `RECEIPT_BUCKET` 环境变量：

```
// Access environment variables
const bucketName = process.env.RECEIPT_BUCKET;
if (!bucketName) {
    throw new Error('RECEIPT_BUCKET environment variable is not set');
}
```

## 使用全局状态
<a name="nodejs-handler-state"></a>

在首次调用您的函数之前，Lambda 会在[初始化阶段](lambda-runtime-environment.md#runtimes-lifecycle-ib)运行您的静态代码。初始化期间创建的资源在两次调用之间保留在内存中，因此您可以避免每次调用函数时都必须创建这些资源的情况。

在示例代码中，S3 客户端初始化代码位于主处理程序之外。运行时会在函数处理第一个事件之前初始化客户端，之后所有的调用都可以重复使用该客户端。

## Node.js Lambda 函数的代码最佳实践
<a name="nodejs-best-practices"></a>

构建 Lambda 函数时请遵循以下准则：
+ **从核心逻辑中分离 Lambda 处理程序。**这样您可以创建更容易进行单元测试的函数。
+ **控制函数部署包中的依赖关系。**AWS Lambda 执行环境包含许多库。对于 Node.js 和 Python 运行时，其中包括 AWS SDK。Lambda 会定期更新这些库，以支持最新的功能组合和安全更新。这些更新可能会使 Lambda 函数的行为发生细微变化。要完全控制您的函数所用的依赖项，请使用部署包来打包所有依赖项。
+ **将依赖关系的复杂性降至最低。**首选在[执行环境](lambda-runtime-environment.md)启动时可以快速加载的更简单的框架。
+ **将部署包大小精简为只包含运行时必要的部分。**这样会减少调用前下载和解压缩部署包所需的时间。

**利用执行环境重用来提高函数性能。**连接软件开发工具包 (SDK) 客户端和函数处理程序之外的数据库，并在 `/tmp` 目录中本地缓存静态资产。由函数的同一实例处理的后续调用可重用这些资源。这样就可以通过缩短函数运行时间来节省成本。

为了避免调用之间潜在的数据泄露，请不要使用执行环境来存储用户数据、事件或其他具有安全影响的信息。如果您的函数依赖于无法存储在处理程序的内存中的可变状态，请考虑为每个用户创建单独的函数或单独的函数版本。

**使用 keep-alive 指令来维护持久连接。**Lambda 会随着时间的推移清除空闲连接。在调用函数时尝试重用空闲连接会导致连接错误。要维护您的持久连接，请使用与运行时关联的 keep-alive 指令。有关示例，请参阅[在 Node.js 中通过 Keep-Alive 重用连接](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-reusing-connections.html)。

**使用[环境变量](configuration-envvars.md)将操作参数传递给函数。**例如，您在写入 Amazon S3 存储桶时，不应对要写入的存储桶名称进行硬编码，而应将存储桶名称配置为环境变量。

**避免在 Lambda 函数中使用递归调用**，在这种情况下，函数会调用自己或启动可能再次调用该函数的进程。这可能会导致意想不到的函数调用量和升级成本。如果您看到意外的调用量，请立即将函数保留并发设置为 `0` 来限制对函数的所有调用，同时更新代码。

Lambda 函数代码中**不要使用非正式的非公有 API**。对于 AWS Lambda 托管式运行时，Lambda 会定期为 Lambda 的内部 API 应用安全性和功能更新。这些内部 API 更新可能不能向后兼容，会导致意外后果，例如，假设您的函数依赖于这些非公有 API，则调用会失败。请参阅 [API 参考](https://docs.aws.amazon.com/lambda/latest/api/welcome.html)以查看公开发布的 API 列表。

**编写幂等代码。**为您的函数编写幂等代码可确保以相同的方式处理重复事件。您的代码应该正确验证事件并优雅地处理重复事件。有关更多信息，请参阅[如何使我的 Lambda 函数具有幂等性？](https://aws.amazon.com/premiumsupport/knowledge-center/lambda-function-idempotent/)。