Skip to main content

如何编写云函数

函数入参详解

每个云函数调用都会收到两个重要对象:eventcontext

event 对象

event 对象包含触发云函数的事件数据,其内容根据触发方式不同而变化:

  • 小程序调用:包含小程序端传入的参数
  • HTTP 请求调用:包含 HTTP 请求信息(如请求头、请求体等)
  • 定时触发:包含定时触发的相关信息

context 对象

context 对象提供调用上下文信息,帮助您了解函数的运行环境和调用方式:

  • 请求 ID:当前调用的唯一标识符
  • 调用来源:触发函数的服务或客户端信息
  • 执行环境:函数的运行时信息
  • 用户身份:调用方的身份信息(如有)

基础代码示例

以下是一个简单的 Node.js 云函数示例,展示如何处理入参并返回结果:

// index.js - 云函数入口文件
exports.main = async (event, context) => {
// 1. 解析云函数入参
const { a, b } = event;

// 2. 执行业务逻辑
const sum = a + b;

// 3. 返回结果
return {
sum,
timestamp: Date.now(),
requestId: context.requestId
};
};

异步处理最佳实践

由于实例的管理由平台自动处理,推荐云函数采用 async/await 模式,避免使用 Promise 链式调用:

exports.main = async (event, context) => {
// ❌ 不推荐:Promise 链式调用
getList().then((res) => {
// do something...
});

// ✅ 推荐:使用 async/await
const res = await getList();
// do something...
};

环境变量使用

云函数可以通过 process.env 获取环境变量,这是管理配置信息的最佳实践:

获取环境变量

exports.main = async (event, context) => {
// 获取环境变量
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
const nodeEnv = process.env.NODE_ENV || 'development';

// 使用环境变量进行配置
const config = {
database: dbUrl,
apiKey: apiKey,
debug: nodeEnv === 'development'
};

return {
message: '环境变量获取成功',
environment: nodeEnv
};
};

环境变量最佳实践

exports.main = async (event, context) => {
// 检查必需的环境变量
const requiredEnvVars = ['DATABASE_URL', 'API_KEY'];
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);

if (missingVars.length > 0) {
throw new Error(`缺少必需的环境变量: ${missingVars.join(', ')}`);
}

// 安全地使用环境变量
const config = {
dbUrl: process.env.DATABASE_URL,
apiKey: process.env.API_KEY,
timeout: parseInt(process.env.TIMEOUT) || 5000
};

return { success: true, config };
};
注意
  • 敏感信息(如 API 密钥、数据库连接字符串)应通过环境变量传递,不要硬编码在代码中
  • 环境变量值始终是字符串类型,需要时请进行类型转换
  • 建议为环境变量设置默认值,提高代码的健壮性

时区设置

云函数的运行环境内保持的是 UTC 时间,即 0 时区时间,和北京时间有 8 小时的时间差。

可以通过语言的时间处理相关库或代码包(如 moment-timezone),识别 UTC 时间并转换为+8 区北京时间。

注意

当前云开发支持的函数版本为 Node 10,无法通过设置环境变量 TZ=Asia/Shanghai 指定时区(Node 15+版本支持)。

时区处理示例

const moment = require("moment-timezone"); // 需在 package.json 中指定并安装依赖

exports.main = async (event, context) => {
// javascript date
console.log(new Date()); // 2021-03-16T08:04:07.441Z (UTC+0)
console.log(moment().tz("Asia/Shanghai").format()); // 2021-03-16T16:04:07+08:00 (UTC+8)

// 获取当前北京时间
const beijingTime = moment().tz("Asia/Shanghai");

return {
utcTime: new Date().toISOString(),
beijingTime: beijingTime.format(),
timestamp: beijingTime.valueOf()
};
};

时区处理最佳实践

exports.main = async (event, context) => {
const moment = require("moment-timezone");

// 统一时区处理函数
const getBeijingTime = (date = new Date()) => {
return moment(date).tz("Asia/Shanghai");
};

// 格式化时间输出
const formatTime = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
return getBeijingTime(date).format(format);
};

// 业务逻辑中使用
const currentTime = getBeijingTime();
const formattedTime = formatTime();

console.log('当前北京时间:', formattedTime);

return {
success: true,
currentTime: formattedTime,
timestamp: currentTime.valueOf()
};
};

使用 ES Module 规范

在云函数 Node.js 环境中无法直接采用 ES Module 规范编写代码,主要原因在于,云函数默认支持的入口文件(index.js)必须遵循 CommonJS 规范,并且文件名必须为 「index.js」。然而,Node.js 对于符合 ES Module 规范的模块文件要求其扩展名为 .mjs

全新云函数中使用 ES Module

1. 创建 Nodejs v14+ 环境云函数

提示

Nodejs 版本必须大于等于14才支持 ES Module。

2. 创建入口文件 entry.mjs

创建一个名为 entry.mjs 的文件,作为 ES Module 入口点:

// entry.mjs
import { foo } from './util.js';

// 入口函数
export const entry = (event, context) => {
return foo(event, context);
};

3. 在 index.js 中引入入口文件

// index.js
exports.main = async (event, context) => {
const { entry } = await import('./entry.mjs');
return entry(event, context);
};

4. 创建业务模块

// util.js
export const foo = async (event, context) => {
console.log('使用 ES Module 语法');

return {
success: true,
message: 'ES Module 调用成功',
data: event
};
};

改造现有云函数支持 ES Module

对于现有的云函数代码想要适配 ES Module 规范,本着对代码最小侵入性以及改造成本最低原则:

1. 创建入口文件 entry.mjs

// entry.mjs

// 假设src文件夹是现有的业务代码
import { businessLogic } from './src/index.js';

// 入口函数
export const entry = (event, context) => {
return businessLogic(event, context);
};

2. 创建 package.json 文件

在业务代码目录下创建 package.json 文件:

{
"type": "module"
}

这将告诉 Node.js 将此目录视为 ES Module,并允许使用 ESM 语法导入和导出模块。

3. 修改现有业务代码

将现有的 CommonJS 语法改为 ES Module 语法:

// src/index.js - 改造前(CommonJS)
const helper = require('./helper');

module.exports = {
businessLogic: async (event, context) => {
return helper.process(event);
}
};

// src/index.js - 改造后(ES Module)
import { process } from './helper.js';

export const businessLogic = async (event, context) => {
return process(event);
};

ES Module 最佳实践

// entry.mjs
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
import { dirname } from 'path';

// 在 ES Module 中使用 require(如果需要)
const require = createRequire(import.meta.url);

// 获取当前文件路径(ES Module 中的 __dirname 替代)
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// 动态导入
const loadModule = async (moduleName) => {
try {
const module = await import(moduleName);
return module;
} catch (error) {
console.error(`模块加载失败: ${moduleName}`, error);
throw error;
}
};

export const entry = async (event, context) => {
// 条件导入
const moduleName = event.moduleType || 'default';
const handler = await loadModule(`./handlers/${moduleName}.js`);

return handler.default(event, context);
};

错误处理与日志记录

错误处理最佳实践

exports.main = async (event, context) => {
try {
// 参数验证
if (!event.userId) {
throw new Error('缺少必需参数: userId');
}

// 业务逻辑处理
const result = await processUserData(event.userId);

return {
success: true,
data: result
};
} catch (error) {
// 记录错误日志
console.error('函数执行失败:', {
error: error.message,
stack: error.stack,
event,
requestId: context.requestId
});

// 返回友好的错误信息
return {
success: false,
error: error.message,
requestId: context.requestId
};
}
};

日志记录规范

exports.main = async (event, context) => {
// 记录函数开始执行
console.log('函数开始执行:', {
requestId: context.requestId,
event: event
});

const startTime = Date.now();

try {
const result = await businessLogic(event);

// 记录成功日志
console.log('函数执行成功:', {
requestId: context.requestId,
duration: Date.now() - startTime,
resultSize: JSON.stringify(result).length
});

return result;
} catch (error) {
// 记录错误日志
console.error('函数执行失败:', {
requestId: context.requestId,
duration: Date.now() - startTime,
error: error.message
});

throw error;
}
};

性能优化建议

执行时间优化

exports.main = async (event, context) => {
const startTime = Date.now();

try {
// 使用并行处理提升性能
const promises = event.items.map(item => processItem(item));
const results = await Promise.all(promises);

const duration = Date.now() - startTime;
console.log(`函数执行耗时: ${duration}ms`);

return {
success: true,
data: results,
duration
};
} catch (error) {
console.error('执行错误:', error);
throw error;
}
};

内存使用优化

exports.main = async (event, context) => {
// 分批处理大数据,避免内存溢出
const batchSize = parseInt(process.env.BATCH_SIZE) || 100;
const results = [];

for (let i = 0; i < event.data.length; i += batchSize) {
const batch = event.data.slice(i, i + batchSize);
const batchResult = await processBatch(batch);
results.push(...batchResult);

// 及时清理不需要的变量
batch.length = 0;

// 记录处理进度
console.log(`已处理 ${Math.min(i + batchSize, event.data.length)}/${event.data.length} 条数据`);
}

return results;
};

连接池优化

// 在函数外部初始化连接池,实现连接复用
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 10,
acquireTimeout: 60000,
timeout: 60000
});

exports.main = async (event, context) => {
let connection;

try {
// 从连接池获取连接
connection = await pool.getConnection();

const [rows] = await connection.execute(
'SELECT * FROM users WHERE id = ?',
[event.userId]
);

return {
success: true,
data: rows
};
} catch (error) {
console.error('数据库操作失败:', error);
throw error;
} finally {
// 释放连接回连接池
if (connection) {
connection.release();
}
}
};

常见问题排查

函数超时问题

问题现象:函数执行时间超过配置的超时时间

排查步骤

  1. 查看监控数据中的执行时间分布
  2. 检查日志中的具体执行步骤耗时
  3. 分析代码中的耗时操作

解决方案

exports.main = async (event, context) => {
// 设置超时处理
const timeout = parseInt(process.env.FUNCTION_TIMEOUT) || 30000;
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('函数执行超时')), timeout - 1000);
});

try {
// 使用 Promise.race 实现超时控制
const result = await Promise.race([
businessLogic(event),
timeoutPromise
]);

return result;
} catch (error) {
if (error.message === '函数执行超时') {
console.error('函数执行超时,请优化代码逻辑');
}
throw error;
}
};

内存不足问题

问题现象:函数运行时内存使用超过配置限制

排查步骤

  1. 查看监控数据中的内存使用情况
  2. 检查代码中的大对象创建和使用
  3. 分析是否存在内存泄漏

解决方案

exports.main = async (event, context) => {
// 监控内存使用
const memoryUsage = process.memoryUsage();
console.log('初始内存使用:', memoryUsage);

try {
// 分批处理大数据
const results = [];
const batchSize = 50; // 根据内存限制调整批次大小

for (let i = 0; i < event.data.length; i += batchSize) {
const batch = event.data.slice(i, i + batchSize);
const batchResult = await processBatch(batch);
results.push(...batchResult);

// 强制垃圾回收(如果可用)
if (global.gc) {
global.gc();
}

// 检查内存使用
const currentMemory = process.memoryUsage();
console.log(`批次 ${Math.floor(i/batchSize) + 1} 内存使用:`, currentMemory);
}

return results;
} catch (error) {
console.error('内存使用异常:', process.memoryUsage());
throw error;
}
};

高错误率问题

问题现象:函数执行失败率较高

排查步骤

  1. 查看日志中的错误信息
  2. 分析错误类型和出现频率
  3. 检查外部依赖服务状态

解决方案

// 重试机制实现
async function retryOperation(operation, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
console.warn(`操作失败,第 ${i + 1} 次重试:`, error.message);

if (i === maxRetries - 1) {
throw error; // 最后一次重试失败,抛出错误
}

// 指数退避延迟
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
}

exports.main = async (event, context) => {
try {
// 参数校验
if (!event || typeof event !== 'object') {
throw new Error('无效的事件参数');
}

// 使用重试机制调用外部服务
const result = await retryOperation(async () => {
return await callExternalAPI(event.data);
});

return {
success: true,
data: result
};
} catch (error) {
// 详细的错误日志
console.error('函数执行失败:', {
error: error.message,
stack: error.stack,
event,
requestId: context.requestId,
timestamp: new Date().toISOString()
});

return {
success: false,
error: error.message,
requestId: context.requestId
};
}
};

开发调试技巧

本地调试

// 本地调试时的环境检测
const isLocal = process.env.NODE_ENV === 'development' || !process.env.TENCENTCLOUD_RUNENV;

exports.main = async (event, context) => {
// 本地调试时提供模拟数据
if (isLocal) {
console.log('本地调试模式');
event = event || { userId: 'test-user-123' };
context = context || { requestId: 'local-request-id' };
}

// 调试信息输出
if (process.env.DEBUG === 'true') {
console.log('调试信息:', {
event,
context,
env: process.env.NODE_ENV
});
}

return await businessLogic(event, context);
};

性能监控

exports.main = async (event, context) => {
const startTime = process.hrtime.bigint();
const startMemory = process.memoryUsage();

try {
const result = await businessLogic(event);

// 性能统计
const endTime = process.hrtime.bigint();
const endMemory = process.memoryUsage();
const duration = Number(endTime - startTime) / 1000000; // 转换为毫秒

console.log('性能统计:', {
duration: `${duration.toFixed(2)}ms`,
memoryDelta: {
rss: endMemory.rss - startMemory.rss,
heapUsed: endMemory.heapUsed - startMemory.heapUsed
}
});

return result;
} catch (error) {
console.error('执行异常:', error);
throw error;
}
};

通过合理使用测试、日志和监控功能,可以有效提升云函数的开发效率和运行稳定性。建议开发者在函数开发过程中充分利用这些工具,及时发现和解决问题。