跳到主要内容

函数编写指南

本文介绍如何编写基于 云函数2.0 的函数代码,如何进行错误处理,以及函数结构、出入参等基本信息,并提供一系列的示例以供参考。

快速开始

第一步:编写函数

  1. 在本地创建一个空的文件夹,作为项目的根目录。
  2. 新建 index.js 文件,作为函数的入口文件。
  3. index.js 文件中,填写以下内容:
exports.main = function (event, context) {
const { httpContext } = context
const { url, httpMethod } = httpContext
return `[${httpMethod}][${url}] Hello world!`
}

当前代码为简单的函数示例,云函数2.0 在框架层面支持 单实例多函数及函数路由,即多个函数跑在同一个函数实例上,具体可参考:单实例多函数及函数路由

第二步:运行函数

云函数代码编写完毕后,既可以将函数运行起来,之后便可以通过 HTTP 请求来调用函数。

运行函数的方式有两种:

1. 通过命令行工具本地运行,通常在开发调试阶段

首先需要全局安装 tcb-ff 命令行工具:

npm install -g @cloudbase/functions-framework

在函数代码根路径下执行以下命令,即可本地运行函数:

npx tcb-ff .

当前 tcb cli 工具正式版本还未集成 tcb-ff 相关功能,后续会进行支持。

2. 部署在云端云函数2.0环境中

可在云开发平台创建 云函数2.0 云端服务后,部署云函数代码到 云函数2.0 云端环境中。 部署完成后可获得云端访问地址,即可进行访问。

将代码部署到 云函数2.0 可跳转 云开发平台

第三步:调用函数

1. 调用本地运行的云函数

curl http://localhost:3000/

2. 调用云端环境中运行的云函数2.0服务

云端环境中运行的云函数2.0服务底层是基于云托管的,可以通过云托管的访问方式来访问云函数。

  1. 微信小程序中通过 wx.cloud.callContainer 方式进行调用,见:Cloud.callContainer
  2. 通过 服务域名 进行调用,在 云函数2.0服务页面 可以查询到域名信息。
  3. 通过 HTTP访问服务 进行调用,在 访问服务页面 配置路由后关联到对应资源后,即可进行调用。
2.1 微信小程序中通过 callContainer 调用
// 容器调用必填环境id,不能为空
const c1 = new wx.cloud.Cloud({
resourceEnv: '环境id'
})
await c1.init()

const r = await c1.callContainer({
path: '/', // 填入业务自定义路径
header: {
'X-WX-SERVICE': 'xxx', // 填入服务名称
},
// 其余参数同 wx.request
method: 'POST',
})
console.log(r)

通过该方式调用,您可以省去配置自定义域名、证书等操作,快速调用函数,并获得微信私密链路的安全性提升。

更多信息可参考 wx.cloud.callContainer 相关文档:

2.2 通过 服务域名 进行调用

云函数2.0服务页面 可以查询到域名信息,以下示例为默认域名:

curl https://{}.run.tcloudbase.com/
2.3 通过 HTTP访问服务 进行调用

访问服务页面 配置路由后关联到对应资源后,即可进行调用,以下示例为默认域名:

curl https://{}.app.tcloudbase.com/

了解函数

本部分内容主要介绍函数的基本结构、输入输出、错误处理、函数日志等内容。

云开发提供了 入口main函数TypeScript 类型定义 @cloudbase/functions-typings@v-1 辅助代码编写,可参考:函数类型定义

函数的输入参数 eventcontext

函数的基本结构如下:

exports.main = function (event, context) {
// 函数逻辑
}

exports.main = async function (event, context) {
// 函数逻辑
}

该文件导出一个 main 函数,该函数即为函数的入口方法,当请求到达时,该方法会作为代码执行的起点。该方法接受两个固定的参数 eventcontext,分别代表函数的输入和上下文。

其中,event 为函数的触发事件,在当前的 HTTP 访问场景下,可以认为是 HTTP 请求体,例如 POST 请求的 bodymultipart/form-data 请求的 form-dataPUT 请求所传输的二进制文件等。如果 HTTP 请求没有传递请求体,event 将会是一个空对象。

context 为函数执行的上下文信息,包含以下属性和方法:

Name类型含义
eventIDstring事件唯一标识,用于关联上下文
eventTypestring事件类型,当前固定为 http
timestampnumber请求时间戳
httpContexthttpBasisHTTP 请求的相关信息
extendedContextRecord<string, unknown>扩展的上下文信息,平台提供,包含 环境相关信息
sse()ISeverSentEvent返回用于发送 Server-Sent Event 格式响应的对象

httpBasis 定义如下:

名称类型含义
urlstring本次请求的完整 URL
httpMethodstring本次请求的 HTTP 方法,如 GET
headersIncomingHttpHeaders本次请求的 HTTP 头部

其中,从 extendedContext 中可以获得以下属性:

{
envId: "xxx", // 环境 ID
uin: "xxx", // 请求的 UIN
userId: "xxx", // 请求的用户 ID
accessToken: "xxx", // 调用请求时的 AccessToken
serviceName: "xxx", // 云托管服务名称
serviceVersion: "xxx", // 云托管服务版本
source: "xxx" // 请求来源,如 wx
wechatContext: {
callId: "xxx", // 微信调用 ID
source: "xxx", // 请求来源,如 wx
appId: "xxx", // 小程序的 AppID
openId: "xxx", // 用户的 OpenID
unionId: "xxx", // 资源共享时的 UnionID
fromOpenId: "xxx", // 资源共享时的 fromOpenID
fromUnionId: "xxx", // 资源共享时的 fromUnionID
fromAppId: "xxx" // 资源共享时的 fromAppID
}
}

云函数支持 SSE(Server-Sent Event) 格式的响应,通过调用 sse() 即可以切换到 SSE 模式并得到 ISeverSentEvent 实例。

interface ISeverSentEvent {
setEncoder(encoder: TextEncoder): void;
send(event: SseEvent): boolean;
end(msg: SseEvent): void;
}

interface SseEvent<T = string | Record<string, unknown>> {
data: T;
comment?: string;
event?: string;
id?: string;
retry?: number;
}

可以通过 send() 方法来发送 SSE 响应,例如:

const { sse } = context
sse.send({
data: "Hello world!"
})

更多 SSE 使用示例可参考:使用-server-sent-event-推送消息

当函数执行完毕(即从入口函数返回)时,返回值将会被直接返回给客户端,不会进行额外的序列化处理。

函数的输出(即返回值)

函数执行最后的返回值将作为最终返回值返回给客户端,此时,将使用 默认的HTTP状态码(如正常返回时200、未带有响应体时204、抛出异常时500)和 默认的HTTP响应头 返回给客户端。

函数支持 同步、异步 两种形式的写法,支持 普通类型 | Promise 类型 的返回值,通过 Buffer | Stream 类型的返回值,可以直接返回二进制数据,实现下载文件的能力。

注意:函数返回后,仍可能会有异步操作在后台执行,但这些操作不会影响函数的返回值。

云函数的返回值可以分为 普通响应集成响应 两种形式。 函数框架通过返回值的方式来区分两种类型的响应,如果返回值格式为 {statusCode: number, headers: { [key: string]: string | string[] }} 即为集成响应,其他为普通响应。

普通响应

普通响应 直接将 返回值 完整的返回给客户端。

集成响应

通过集成响应,可以自定义 HTTP 响应的结构,包括响应状态码、头部字段等,可以采用集成响应的返回结构。集成响应的定义如下:

interface IntegrationResponse {
statusCode: number
headers: { [key: string]: string | string[] }
body?: unknown
}

statusCode 为 HTTP 响应状态码,headers 为自定义的 HTTP 响应头部,body 为 HTTP 响应体。例如返回下面的集成响应:

exports.main = function(event, context) {
return {
statusCode: 200,
headers: {
"Content-Type": "text/html"
},
body: {
message: "<h1>Hello world!</h1>"
}
}
}

将可以在浏览器中渲染出 HTML。

错误处理

函数运行过程中可能或抛出异常,异常整体可以分为以下几类:

  1. 不可恢复的错误:如启动时未加载成功代码、Out of Memory(OOM)、堆栈溢出,这类错误会导致函数无法启动或从运行中退出,此类错误是严重错误,会导致函数实例重启,应当尽量规避;
  2. 请求处理过程中的错误:这类错误通常是可捕获的,函数需要对这些错误进行处理,记录相关日志并返回相关信息给客户端。如果用户代码未捕获相关异常,函数框架将会捕获该错误,记录相关日志并返回框架定义的错误信息给客户端;
  3. 未被捕获的异常 UncaughtExceptionUnhandledRejection:通常发生在异步代码逻辑中,因为是未捕获异常,业务上是感知不到的,这类错误会被框架捕获,并打印相关信息到日志中。因为通常发生在异步逻辑中,该错误不会体现在函数返回值中;
  4. 函数框架报错:函数框架内部可能发生一些错误,可能是函数框架代码存在 Bug,也可能是函数框架的限制,这类错误会被框架捕获,并打印相关信息到日志中;
  5. 网络错误:在客户端往返函数实例的网络链路中也可能出现异常,导致一些错误。该类错误非云函数本身的错误,需结合具体部署平台进行排查。

函数的模块化和依赖安装

如通常的 Node.js 代码一样,函数也可以通过 Javascript 模块化方式来组织代码。对于遵循 CommonJS 规范的模块,可以通过 require 来引入依赖。例如:

// index.js
const { add } = require('./sum')

exports.main = function(event, context) {
return add(1, 2)
}

// sum.js
exports.add = function(a, b) {
return a + b
}

对使用 ECMAScript 模块化(需在 package.json 中声明模块化方式)的代码,可以通过 import 语句来引入依赖。例如:

// index.mjs
import { add } from './sum.mjs'

export function main(event, context) {
return add(1, 2)
}

// sum.mjs
export function add(a, b) {
return a + b
}

// package.json
{
"name": "example",
"version": "1.0.0",
"main": "index.mjs", // 指定入口文件
"type": "module", // 声明模块化方式
"dependencies": {
}
}

对于外部依赖安装,可在 package.json 中声明依赖,函数构建时会根据依赖声明自动安装依赖包。如果目录下存在 package-lock.json 等 lock 文件,可以起到锁定依赖版本的作用。

对于通过云托管部署的云函数,构建过程支持 npmyarnpnpm 包依赖管理工具,会识别代码使用的包管理工具进行依赖安装。

函数日志

在函数代码中打印的日志可以分为两类,一类是函数实例级别日志,即主入口函数之外的日志,这部分日志在函数实例启动时、模块被加载时被打印,跟某次具体的请求无关;另一类是请求级别日志,即某次请求触发、代码执行流程进入主入口函数内部之后才会打印的日志,这部分日志往往跟某次具体的请求相关联,一般可以通过请求的 eventID 搜索得到。

请求级别的日志从内容上划分,又可以分为以下几类:

  1. 每次请求自动打印的结构化访问日志(access log),包括该次请求的基本信息,如处理请求的主机名、访问的 URL、请求方法、响应结果、请求处理耗时等;
  2. 函数代码中主动打印的日志,即代码中使用 console.log 等方法打印的日志,这部分日志会跟 eventID 所关联,有助于排查某次请求的具体执行流程;
  3. 函数框架捕获的异常日志,如 UncaughtExceptionUnhandledRejection ,这部分日志会自动跟 eventID 关联,并打印出异常的堆栈信息等便于排查问题。

日志的逻辑级别如下所示:

函数日志
├── 函数实例日志
├── 请求级别日志
│ ├── 访问日志
│ ├── 代码中打印的日志
│ ├── 框架捕获的异常日志

例如下面的函数代码:

// 函数实例日志
console.log("func initialization started.")

// 函数实例日志
setTimeout(() => {
console.log("timeout log.")
}, 1000)

exports.main = function(event, context) {
// 请求级别日志
console.log({ event, context })

setTimeout(() => { throwReject() }, 1000)
return "Hello world"
}

async function throwReject() {
// 这里会触发未捕获的异常,因为没有捕获,会被框架捕获
// 该异常在 main 中 setTimeout 中抛出,因此不会体现在 main 的返回值中
// 该异常由 函数框架捕获并打印到日志中
Promise.reject(new Error('This is an error.'))
}

// 函数实例日志
console.log("func initialization finish.")

在函数实例启动时,App started. 会被打印。请求到达并处理完毕后,客户端会收到 Hello world 响应,1s 后该请求抛出异常被框架捕获,这次请求的访问日志形如:

{
"@timestamp": "2024-xx-xxTxx:xx:xx.xxxZ", // 日志打印的时间戳
"startAt": "2024-xx-xxTxx:xx:xx.xxxZ", // 请求开始处理的时间
"endAt": "2024-xx-xxTxx:xx:xx.xxxZ", // 请求处理结束的时间
"logType": "accesslog", // 日志类型
"hostname": "host", // 处理请求的主机名
"pid": 13506, // 进程 ID
"version": "x.y.z", // 函数框架版本
"nodeEnv": "", // Node.js 相关的环境变量
"tcbEnv": "", // 云开发相关的环境变量
"eventId": "b0900934-79d7-4441-856f-dd46392a5f91", // 本次请求的 eventID
"method": "GET", // 请求方法
"url": "http://127.0.0.1/", // 请求 URL
"path": "/", // 请求路径
"status": 200, // 响应状态码
"httphost": "127.0.0.1", // 请求的主机名
"ua": "PostmanRuntime/7.39.0", // 请求的 User-Agent
"connection": "keep-alive",
"contentType": "application/json", // 请求的 Content-Type
"clientIp": "::1", // 请求的客户端 IP
"resSize": 12, // 响应体大小
"resContentType": "text/plain", // 响应的 Content-Type
"timeCost": 11, // 本次请求的总耗时
"userTimeCost": 0 // 从函数主入口开始到从入口返回的耗时
}

随后异常会被框架捕获,可通过 eventID(b0900934-79d7-4441-856f-dd46392a5f91) 关联,并打印出如下堆栈:

Unhandled Rejection for: Error: This is an error.
at throwReject (/path/to/your/project/index.js:9:18)
at Timeout._onTimeout (/path/to/your/project/index.js:4:22)

注意:日志对于问题排查是非常重要的,建议在函数代码中适当打印日志,以便排查问题。 但是打印大量日志将会带来其他问题,如日志消耗磁盘空间可能导致磁盘可用空间不足,日志采集组件采集解析文件消耗较多 CPU,日志上报到存储系统会消耗大量网络带宽,存储大量日志导致成本增加,日志写入和查询性能下降延迟高等诸多问题,因此建议适度打印日志。

单实例多函数及函数路由

函数框架支持通过配置文件声明多个函数,并通过请求 path 将不同的请求路由到不同函数上。要使用多函数路由能力,需要添加命名为 cloudbase-functions.json 的配置文件,对不同函数进行声明,并进行路由配置。

cloudbase-functions.json 文件说明:

export interface FunctionDefinition {
name: string // 函数名称
directory: string // 函数目录,相对于函数 functionsRoot 的路径
source?: string // 函数入口文件,默认为 index.js
target?: string // 入口函数名称,默认为 main
triggerPath?: string // 匹配的路径,前缀匹配,与 RouteDefinition 中的 path 作用相同,简化配置
}

export interface RouteDefinition {
functionName: string // 目标函数名称,与 FunctionDefinition 中的 name 对应
path?: string // 匹配的路径,前缀匹配,匹配后会调用 functionName 对应的函数
}

export interface FunctionsConfig {
functionsRoot: string // 函数根目录,会到此目录下查找并加载函数
functions: FunctionDefinition[] // 函数定义,描述函数信息,包括函数名称、目录、入口文件等
routes: RouteDefinition[] // 路由定义,即 path -> functionName
}

函数路由规则说明:

  1. 路由匹配为前缀匹配模式,如:请求路径 /a/a/b/a/b/c 均可匹配 path=/a 的路由规则
  2. 路由匹配遵循最长匹配原则,即匹配到最长的路由规则,例如 /a/b/c 会优先匹配 /a/b 而不会匹配 /a
  3. 路由规则中 path 末尾带 / 的和不带 / 的路由匹配效果相同,例如 /aa/aa/ 是等价的,但不会被认为是冲突的
  4. 路由匹配以 / 分隔为路径单元,路径单元需精确匹配,如 /aaa 不会匹配 /a -> functionA 规则
  5. 一个函数可以有多个路由,即可以通过多个不同路径规则触发同一个函数,例如 /a1/a2 可配置都触发 a 函数
  6. 同一个路由只能指向一个函数,例如:/a 只能指向一个函数,不能同时指向 ab 函数

编辑器支持:使用 VSCode 开发时,可以在 .vscode/settings.json 中声明如下配置以获得 cloudbase-functions.json 的 schema 提示:

{
"json.schemas":[
{
"fileMatch":[
"cloudbase-functions.json"
],
"url":"./node_modules/@cloudbase/functions-framework/functions-schema.json"
}
]
}

示例

假设有如下目录结构:

.
├── cloudbase-functions.json # 多函数配置文件
├── index.js # 默认函数入口文件
├── echo # 函数 echo
│ └── echo.js # 函数 echo 入口文件
├── sse # 处理 SSE 请求的函数 sse
│ ├── index.mjs
│ └── package.json
├── sum # 函数 sum
│ ├── index.js
│ ├── package.json
│ └── sum.js
└── ws # 处理 websocket 请求的函数 ws
├── client.mjs
├── index.mjs
└── package.json

cloudbase-functions.json 配置文件中,需要对默认函数(index.js)、函数 echossesumws 共 5 个函数进行声明和路由,配置文件如下:

{
"functionsRoot":".", // 函数根目录,指定为当前目录
"functions":[ // 声明各函数及其入口文件
{
"name":"default", // 声明默认函数,即入口为 index.js 的函数
"directory":"." // 函数目录为当前目录
},
{
"name":"echo", // 声明函数 echo
"directory":"echo", // 声明 echo 函数所在目录为 echo (相对于函数根目录)
"source":"echo.js", // 函数入口文件为 echo.js
"triggerPath": "/echo" // 函数触发路径,即请求 path 匹配 /echo 时,路由至函数 echo
},
{
"name":"sum", // 声明函数 sum
"directory":"sum"
},
{
"name":"ws", // 声明函数 ws
"directory":"ws"
},
{
"name":"sse", // 声明函数 sse
"directory":"sse"
}
],
"routes":[ // 声明路由规则,可通过 triggerPath 方式简化路由规则配置
{
"functionName":"sum", // 路由至函数 sum,名称需要与上述定义的函数名称对应
"path":"/sum" // 满足请求 path 前缀匹配 /sum 条件的请求匹配该规则
},
// path 前缀匹配 /echo 的请求路由至函数 echo
{
"functionName":"echo",
"path":"/echo"
},
// path 前缀匹配 /ws 的请求路由至函数 ws
{
"functionName":"ws",
"path":"/ws"
},
// path 前缀匹配 /sse 的请求路由至函数 sse
{
"functionName":"sse",
"path":"/sse"
},
// 请求默认匹配到 default 函数
{
"functionName":"default",
"path":"/"
}
]
}

使用该配置文件启动服务,根据路径的不同,请求会被路由至不同的函数:

  • path 前缀匹配 /sum 时,路由至函数 sum
  • path 前缀匹配 /echo时,路由至函数 echo
  • path 前缀匹配 /ws 时,路由至函数 ws
  • path 前缀匹配 /sse 时,路由至函数 sse
  • 未能匹配任一规则的请求,路由至函数 default

使用 TypeScript 编写函数代码

相比 JavaScript 通过 TypeScript 编写代码可以获得诸多好处,例如:

  • 更好的开发体验:编辑器可以提供更好的代码补全、重构和导航功能。这使得开发过程更加高效
  • 语法特性TypeScript 支持最新的 ECMAScript 特性,如装饰器、泛型、异步函数等
  • 静态类型检查:可以在编译时捕获类型错误,减少运行时错误,提高代码的可靠性
  • 接口和类型别名TypeScript 支持接口和类型别名,允许开发者定义复杂的数据结构。这使得代码更加模块化和可重用
  • 可读性和可维护性:通过明确的类型定义,TypeScript 代码通常更易于理解和维护
  • 更好的文档:类型定义本身可以作为文档,帮助开发者理解函数和模块的用法,而不需要额外的文档

推荐使用 TypeScript 编写函数代码,尤其是相对复杂一点儿的项目中。

TS类型定义

云函数入口的格式是在入口文件导出一个 main 函数,如:

// index.ts
export const main = function(event, context) {}
// 等价于
export const main = function(event: any, context: any) any {}

如上示例的函数声明中,函数入参 event, context 以及 函数的返回值 均为 any 类型。 参数和返回值类型不明确,导致没有良好的基于类型的代码补全能力,也没有类型检查的约束能力,不利于代码的维护和调试。

针对以上 参数和返回值 类型的不同特点:

  • event 是由业务逻辑定义的,需根据业务需要进行类型定义,并指定
  • context 是云函数框架提供的,由函数框架定义
  • 函数的返回值 也是由业务逻辑定义的,需根据业务需要进行类型定义,并指定

云函数框架提供了云函数的入口函数类型定义,可以增强对参数和返回值类型的感知和约束能力。

可以通过安装 @cloudbase/functions-typings 包来获取云函数的入口函数类型定义:

npm install @cloudbase/functions-typings@v-1

安装完成后,即可在函数代码中引入云函数的入口函数类型定义:

import { TcbEventFunction } from '@cloudbase/functions-typings'

如上示例中导入的 TcbEventFunction 为云函数的入口函数类型定义,支持定义函数的 eventcontext 参数类型,以及函数的 返回值 类型。

该类型是一个泛型类型 TcbEventFunction<EventT = unknown, ResultT extends ReturnT = void>

  1. 通过 EventT 可定义 event 参数的类型
  2. 通过 ResultT 可定义函数的 返回值 的类型

这两个类型具有业务逻辑决定,这里开放了灵活的定义方式。

定义函数的 event 参数类型

可通过 TcbEventFunction<EventT = unknown, ResultT extends ReturnT = void> 的第一个泛型参数 EventT 来指定函数 event 参数的类型。

如下为定义 event 参数类型一个示例:

type jsonEvent = {a: number, b: string, c: boolean, d: {e: string}}
export const main: TcbEventFunction<jsonEvent> = function(event, context) {
event.a
event.b
}

如果是通过 FormData 方式进行文件上传,函数框架会将文件上传时对应的字段放在 event 入参的对应字段中,类型为 File 类型,用于描述文件信息,例如文件名称、文件大小、文件路径等。 可通过如下示例的方式定义包含文件 Fileevent 参数类型:

import { TcbEventFunction, File } from '@cloudbase/functions-typings'
type formEvent = {str: string, file: File}
export const main: TcbEventFunction<formEvent> = function(event, context) {
event.str
event.file.filepath
}

定义函数的 返回值 类型

可通过 TcbEventFunction<EventT = unknown, ResultT extends ReturnT = void> 的第二个泛型参数 ResultT 来指定函数的返回值类型。

函数返回值支持 普通响应集成响应 两种类型。

普通响应直接指定具体类型即可,普通响应类型示例:

import { TcbEventFunction } from '@cloudbase/functions-typings'

// 普通响应-有返回值,返回值类型为 string
// ReturnT = string
export const main: TcbEventFunction<void, string> = function(event, context) {
return 'done.'
}

集成响应可通过 IntegrationResponse<BodyT = unknown> 进行定义,通过 BodyT 可以定义响应体类型,如下示例:

import { TcbEventFunction, IntegrationResponse } from '@cloudbase/functions-typings'

// 集成响应,返回值 body 类型为 string
// ReturnT = IntegrationResponse<string>
export const main: TcbEventFunction<void, IntegrationResponse<string>> = function(event, context) {
return {
statusCode: 200,
headers: {},
body: ''
}
}

函数的返回值类型支持 Promise 类型,如 ReturnT = Promise<string>ReturnT = Promise<IntegrationResponse<string>>

如下风格的函数定义,也可以指定类型,但是会略麻烦,推荐使用如上描述的风格。

export function main(event, context) {}

完整示例

import { TcbEventFunction, File, IntegrationResponse } from '@cloudbase/functions-typings'

// GET no boyd
export const main: TcbEventFunction = function(event, context) {
}

// Content-Type: application/json
type jsonEvent = {a: number, b: string}
export const main: TcbEventFunction<jsonEvent> = function(event, context) {
event.a
event.b
}

// Content-Type: multipart/form-data
type formEvent = {str: string, file: File}
export const main: TcbEventFunction<formEvent> = function(event, context) {
event.str
event.file.filepath
}

// Content-Type: application/octet-stream
export const main: TcbEventFunction<Buffer> = function(event, context) {
event.byteLength
}

// 访问 Context 信息
export const main: TcbEventFunction<void, void> = function(event, context) {
context.extendedContext?.envId
context.extendedContext?.userId
}

// 普通响应-无返回值
export const main: TcbEventFunction = function(event, context) {
return
}

// 普通响应-有返回值
export const main: TcbEventFunction<void, string> = function(event, context) {
return 'done.'
}

// 集成响应
export const main: TcbEventFunction<void, IntegrationResponse<string>> = function(event, context) {
return {
statusCode: 200,
headers: {},
body: ''
}
}

// 异步函数
export const main: TcbEventFunction<void, Promise<string>> = async function(event, context) {
return new Promise((resolve) => {
setImmediate(() => {
resolve('done.')
})
})
}

// SSE
export const main: TcbEventFunction<void, Promise<string>> = async function(event, context) {
const sse = context.sse?.()

if (sse && !sse.closed) {
sse.on('close', () => {
console.log('sse closed')
})

sse.send({ data: 'hello from sse function, abcedfg..., €€€€€⭐⭐⭐⭐⭐' })

sse.send({ data: 'message with linebreak symbol:\\nThis is the first line.\\n\\nThis is the second line.\\r\\r\\r\n' })

// 单次发送多个事件
sse.send([
{ data: 'This is the first message.' },
{ data: 'This is the second message, it\n\r\r\n\r\r\rhas two lines.' },
{ data: 'This is the third message.' }
])

// 以下为发送原始消息的示例
// 该方式用于扩展 SSE 协议,例如发送其他 Event Field 字段
// 注意:末尾必须有换行符数据才会立即发送

sse.send('message: This is a raw message. ')
sse.send(['message: This is another raw message.\n\n'])

const msgs = [
'This is a raw message. \n',
'This is another raw message.\n\n'
]
sse.send(msgs)
}
return ''
}

// WebSocket
export const main: TcbEventFunction = async function(event, context) {
if (context.ws) {
context.ws.on('open', (msg) => {
console.log('open: ', msg)
})
context.ws.on('error', (msg) => {
console.log('error: ', msg)
})
context.ws.on('close', (msg) => {
console.log('close: ', msg)
})
context.ws.on('message', (msg) => {
console.log('message: ', msg)
})
context.ws.send('hello from websocket function')
}
}
main.handleUpgrade = async function(context) {
return {
allowWebSocket: true
}
}

项目组织结构

基于 TypeScript 的项目结构

.
├── README.md # 项目说明
├── cloudrunfunctions # 云函数2.0项目代码目录,每个目录对应一个函数
│ ├── README.md # 云函数说明
│ ├── func-a # 云函数 `func-a` 代码目录,单函数示例多函数
│ │ ├── README.md # 当前云函数说明
│ │ ├── built # TypeScript 编译后代码目录
│ │ ├── src # TypeScript 源代码目录
│ │ ├── package.json
│ │ ├── cloudbase-functions.json # 云函数声明及配置文件
│ │ └── tsconfig.json # TypeScript 配置文件
│ └── func-b # 云函数 `func-b` 代码目录,单函数示例单函数
│ ├── README.md # 当前云函数说明
│ ├── built # TypeScript 编译后代码目录
│ ├── src # TypeScript 源代码目录
│ ├── package.json
│ └── tsconfig.json # TypeScript 配置文件
├── node_modules # node_modules [可能有]
├── package.json # package.json [可能有]
├── tsconfig.json # tsconfig.json [可能有]

该项目结构对应源代码见:https://github.com/TencentCloudBase/cloudbase-examples/tree/master/cloudrunfunctions/ts-multiple-functions

以上,是一个基于 TypeScript 的项目结构示例,项目中 需要 srcbuilt 目录 包含 源代码和编译后的代码,因此需要在云函数代码中配置 tsconfig.json 文件。 如果是个 JavaScript 编码的项目,则不需要 srcbuilttsconfig.json 等目录和文件。

因每个函数是独立部署的,所以每个函数也是相互独立的,但是代码结构比较相似。

非运行时依赖的部分,可以在多个函数之间共享,如 tsconfig.json 公共依赖 等。

项目中还可能包含很多其他类型的文件,其他文件的组织根据项目需要进行即可。

以上是一个相对复杂的项目结构,针对较复杂的项目。 如果整个项目目录中,只有一个云函数,也可以采用单函数的方式进行 目录结构 组织,例如如下项目结构:

.
├── README.md # 当前云函数说明
├── built # TypeScript 编译后代码目录
├── src # TypeScript 源代码目录
├── package.json
├── cloudbase-functions.json # 云函数声明及配置文件
└── tsconfig.json # TypeScript 配置文件

以目录结构是把 项目根路径/cloudrunfunctions/func-a/* 目录中的文件移动到 项目根路径/ 目录下,这样的目录结构适用于单函数的场景。

例如模板项目的示例结构:https://github.com/TencentCloudBase/func-v2-template 也是此类型的项目结构。