

# 定义采用 TypeScript 的 Lambda 函数处理程序
<a name="typescript-handler"></a>

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

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

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

## 设置 TypeScript 项目
<a name="typescript-handler-setup"></a>

使用本地集成式开发环境（IDE）或文本编辑器来编写 TypeScript 函数代码。您无法在 Lambda 控制台上创建 TypeScript 代码。

有多种方法可初始化 TypeScript Lambda 项目。例如，您可以使用 `npm` 创建项目、创建 [AWS SAM 应用程序](typescript-package.md#aws-sam-ts)或创建 [AWS CDK 应用程序](typescript-package.md#aws-cdk-ts)。使用 `npm` 创建项目：

```
npm init
```

您的函数代码存位于 `.ts` 文件中，您在编译时将其转换为 JavaScript 文件。您可以使用 [esbuild](https://esbuild.github.io/) 或 Microsoft 的 TypeScript 编译器（`tsc`）将您的 TypeScript 代码转换为 JavaScript。要使用 esbuild，请将其添加为开发依赖项：

```
npm install -D esbuild
```

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

```
/project-root
  ├── index.ts - Contains main handler
  ├── dist/ - Contains compiled JavaScript
  ├── package.json - Project metadata and dependencies
  ├── package-lock.json - Dependency lock file
  ├── tsconfig.json - TypeScript configuration
  └── node_modules/ - Installed dependencies
```

## 示例 TypeScript Lambda 函数代码
<a name="typescript-example-code"></a>

以下示例 Lambda 函数代码接收有关订单的信息，生成文本文件接收，并将此文件放入 Amazon S3 存储桶中。此示例定义了自定义事件类型（`OrderEvent`）。要了解如何导入 AWS 事件源的类型定义，请参阅 [Lambda 的类型定义](lambda-typescript.md#typescript-type-definitions)。

**注意**  
此示例使用了 ES 模块处理程序。Lambda 同时支持 ES 模块和 CommonJS 处理程序。有关更多信息，请参阅 [CommonJS 和 ES 模块](nodejs-handler.md#nodejs-commonjs-es-modules)。

**Example index.ts Lambda 函数**  

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

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

// Define the shape of the input event
type OrderEvent = {
    order_id: string;
    amount: number;
    item: string;
}

/**
 * Lambda handler for processing orders and storing receipts in S3.
 */
export const handler = async (event: OrderEvent): Promise<string> => {
    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 instanceof Error ? error.message : 'Unknown error'}`);
        throw error;
    }
};

/**
 * Helper function to upload receipt to S3
 */
async function uploadReceiptToS3(bucketName: string, key: string, receiptContent: string): Promise<void> {
    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 instanceof Error ? error.message : 'Unknown error'}`);
    }
}
```

此 `index.ts` 文件包含以下代码部分：
+ `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)。
+ `type OrderEvent`：定义预期输入事件的结构。
+ `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="typescript-commonjs-es-modules"></a>

Node.js 支持两个模块系统：CommonJS 和 ECMAScript 模块（ES 模块）。Lambda 建议使用 ES 模块，因为它支持顶层的 await 语句，这使得异步任务能够在[执行环境初始化](#typescript-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 模块。

## Node.js 初始化
<a name="typescript-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="typescript-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="typescript-example-input"></a>

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

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

在使用 TypeScript Lambda 函数时，您可以使用类型或接口来定义输入事件的形状。在此示例中，我们使用了类型来定义事件结构：

```
type OrderEvent = {
    order_id: string;
    amount: number;
    item: string;
}
```

定义类型或接口后，请在处理程序的签名中用其来确保类型安全性：

```
export const handler = async (event: OrderEvent): Promise<string> => {
```

在编译过程中，TypeScript 会验证事件对象是否包含具有正确类型的必填字段。例如，如果您尝试将 `event.order_id` 用作数字或 `event.amount` 字符串，TypeScript 编译器会报告错误。

## TypeScript 函数的有效处理程序模式
<a name="typescript-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 时，您编写的代码看起来与同步代码类似，同时仍然是异步和非阻止式的。

本节中的示例使用了 `S3Event` 类型。但是，您可以使用 [@types/aws-lambda](https://www.npmjs.com/package/@types/aws-lambda) 程序包中的任何其他 AWS 事件类型，也可以定义自有的事件类型。要使用 @types/aws-lambda 中的类型，请执行以下操作：

1. 将 @types/aws-lambda 程序包添加为开发依赖项：

   ```
   npm install -D @types/aws-lambda
   ```

1. 导入所需的类型，例如 `Context`、`S3Event` 或 `Callback`。

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

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

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

```
export const handler = async (event: S3Event): Promise<void> => { };
```

```
export const handler = async (event: S3Event, context: Context): Promise<void> => { };
```

**注意**  
异步处理项目数组时，请务必使用带有 `Promise.all` 的 await 来确保所有操作完成。诸如 `forEach` 这样的方法不会等待异步回调完成。有关更多信息，请参阅 Mozilla 文档中的 [Array.prototype.forEach()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)。

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

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

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

```
export const handler = (event: S3Event, context: Context): void => { };
```

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

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

```
export const handler = awslambda.streamifyResponse(async (event: APIGatewayProxyEvent, responseStream: NodeJS.WritableStream, context: Context) => { });
```

有关更多信息，请参阅 Lambda 函数的响应流式处理。

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

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

基于回调的函数处理程序可以使用事件、上下文和回调参数。回调参数需要一个 `Error` 和一个响应，该响应必须是 JSON 可序列化的。

以下是回调处理程序模式的有效签名：

```
export const handler = (event: S3Event, context: Context, callback: Callback<void>): void => { };
```

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

**Example 包含回调的 TypeScript 函数**  
以下示例使用了 `APIGatewayProxyCallback`，这是一种特定于 API 网关集成的专用回调类型。大多数 AWS 事件源使用了上述签名中显示的通用 `Callback` 类型。  

```
import { Context, APIGatewayProxyCallback, APIGatewayEvent } from 'aws-lambda';

export const lambdaHandler = (event: APIGatewayEvent, context: Context, callback: APIGatewayProxyCallback): void => {
    console.log(`Event: ${JSON.stringify(event, null, 2)}`);
    console.log(`Context: ${JSON.stringify(context, null, 2)}`);
    callback(null, {
        statusCode: 200,
        body: JSON.stringify({
            message: 'hello world',
        }),
    });
};
```

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

通常，您将使用 Lambda 函数与其他 AWS 资源进行交互或对其进行更新。与此类资源最简单的交互方法是使用 适用于 JavaScript 的 AWS SDK。所有支持的 Lambda Node.js 运行时都包含[适用于 JavaScript 版本 3 的 SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/introduction/)。但是，强烈建议您在部署包中包含所需的 AWS SDK 客户端。这样可以最大限度地提高未来 Lambda 运行时更新期间的[向后兼容性](runtimes-update.md#runtime-update-compatibility)。

要向函数添加 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="typescript-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="typescript-handler-state"></a>

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

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

## TypeScript Lambda 函数的代码最佳实践
<a name="typescript-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/)。